From bbf18e7809073f134f58cbd8c98ec14337f4f8d5 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 14 Mar 2023 10:31:28 +0100 Subject: [PATCH 01/99] Add 05-mesh-exploration.py --- .../05-mesh-exploration.py | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 examples/01-Detailed-Examples/05-mesh-exploration.py diff --git a/examples/01-Detailed-Examples/05-mesh-exploration.py b/examples/01-Detailed-Examples/05-mesh-exploration.py new file mode 100644 index 000000000..428701e34 --- /dev/null +++ b/examples/01-Detailed-Examples/05-mesh-exploration.py @@ -0,0 +1,138 @@ +""" +.. _ref_mesh_exploration_example: + +Explore the mesh +================ +In this script a static simulation is used as an example to show how to +query mesh information such as connectivity, element IDs, element types and so on. +""" + +############################################################################### +# Perform required imports +# ------------------------ +# Perform required imports. # This example uses a supplied file that you can +# get by importing the DPF ``examples`` package. + +from ansys.dpf import post +from ansys.dpf.post import examples + +############################################################################### +# Get ``Simulation`` object +# ------------------------- +# Get the ``Simulation`` object that allows access to the result. The ``Simulation`` +# object must be instantiated with the path for the result file. For example, +# ``"C:/Users/user/my_result.rst"`` on Windows or ``"/home/user/my_result.rst"`` +# on Linux. + +example_path = examples.download_crankshaft() +simulation = post.StaticMechanicalSimulation(example_path) + +# print the simulation to get an overview of what's available +print(simulation) + +############################################################################### +# Get the mesh +# ------------ + +mesh = simulation.mesh +print(mesh) + +############################################################################### +# Query basic information about the mesh (available) +# -------------------------------------- + +# Node IDs +n_ids = mesh.node_ids + +# Element IDs +e_ids = mesh.element_ids + +# Available named selection names +named_selections = mesh.available_named_selections + +# Query basic information about the mesh (available in PyDPF-Core) +# -------------------------------------- + +# Number of nodes +n_nodes = mesh._core_object.nodes.n_nodes + +# Number of elements +n_elements = mesh._core_object.elements.n_elements + +# Number of named selections +n_ns = len(named_selections) + +# Unit (get and set) +mesh_unit = mesh._core_object.unit +mesh._core_object.unit = "mm" + +# Get the list of nodes/elements IDs of a given named selection +ns_scoping = mesh._core_object.named_selection(named_selection=named_selections[0]) +ns_ids = ns_scoping.ids + +# Named selection setter +mesh._core_object.set_named_selection_scoping( + named_selection_name=named_selections[0], + scoping=ns_scoping, +) + +# Plot the mesh +plt = mesh._core_object.plot() + +# ####################################### +# Manipulating elements + +# Get an element by ID +element_by_id = mesh._core_object.elements.element_by_id(1) +# Get an element by index +element_by_index = mesh._core_object.elements.element_by_index(0) +# Get the ID of an element +e_id = mesh._core_object.elements.element_by_index(0).id + +# Adding elements to the mesh + +# Get the element types +e_types = mesh._core_object.elements.element_types_field + +# Get the materials +e_materials = mesh._core_object.elements.materials_field + +# Get the elemental connectivity +connectivity = mesh._core_object.elements.connectivities_field + +# ############################################## +# Query information about one particular element + +# Get the nodes of an element +e_nodes = mesh._core_object.elements.element_by_id(1).nodes +# Get the node IDs of an element +e_node_ids = mesh._core_object.elements.element_by_id(1).node_ids +# Get the nodes of an element +e_n_node = mesh._core_object.elements.element_by_id(1).n_nodes + +# Get the type of the element +e_type = mesh._core_object.elements.element_by_id(1).type +# Get the shape of the element +e_shape = mesh._core_object.elements.element_by_id(1).shape +# Get the connectivity of the element +e_connectivity = mesh._core_object.elements.element_by_id(1).connectivity + + +# ################## +# Manipulating nodes + +# Get a node by ID +node_by_id = mesh._core_object.nodes.node_by_id(1) +# Get a node by index +node_by_index = mesh._core_object.nodes.node_by_index(0) + +# Get the coordinates of all nodes +coordinates = mesh._core_object.nodes.coordinates_field + +# ########################################### +# Query information about one particular node + +# Coordinates +coor = mesh._core_object.nodes.node_by_id(1).coordinates +# Nodal connectivity +conn = mesh._core_object.nodes.node_by_id(1).nodal_connectivity From 6995be2561ee8f3f58af4271afe28f328fc5b918 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 14 Mar 2023 15:49:29 +0100 Subject: [PATCH 02/99] Comments on API --- .../01-Detailed-Examples/05-mesh-exploration.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/examples/01-Detailed-Examples/05-mesh-exploration.py b/examples/01-Detailed-Examples/05-mesh-exploration.py index 428701e34..14e0259e0 100644 --- a/examples/01-Detailed-Examples/05-mesh-exploration.py +++ b/examples/01-Detailed-Examples/05-mesh-exploration.py @@ -36,7 +36,22 @@ mesh = simulation.mesh print(mesh) - +# meshes : MeshesContainer = simulation.meshes_with_elements_grouped_by("mat_id") +# meshes : MeshesContainer = simulation.meshes(elements_grouped_by="mat_id") +# meshes : MeshesContainer = simulation.meshes.elements_grouped_by("mat_id") +# meshes : MeshesContainer = simulation.meshes.elements_grouped_by("mat_id") +# meshes : MeshesContainer = simulation.meshes_by_properties("mat_id", "thickness") +# print(meshes) +# """ +# mesh0: {mat_id=1, thickness=1} +# mesh1: {mat_id=1, thickness=2} +# mesh2: {mat_id=2, thickness=1} +# mesh3: {mat_id=2, thickness=2} +# """ +# mesh = simulation.mesh_by_property(mat_id=1) +# mesh_info = simulation.mesh_info -> +# mesh = MeshesContainer.mesh_by_id(mat_id=1) +# for id, mesh for ############################################################################### # Query basic information about the mesh (available) # -------------------------------------- From 0bc4c45640775fc926bec2c09cefd4d7b0f017e5 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 20 Mar 2023 11:14:58 +0100 Subject: [PATCH 03/99] Meeting 20/03 --- .../05-mesh-exploration.py | 103 +++++++++++++++++- 1 file changed, 97 insertions(+), 6 deletions(-) diff --git a/examples/01-Detailed-Examples/05-mesh-exploration.py b/examples/01-Detailed-Examples/05-mesh-exploration.py index 14e0259e0..c33a83525 100644 --- a/examples/01-Detailed-Examples/05-mesh-exploration.py +++ b/examples/01-Detailed-Examples/05-mesh-exploration.py @@ -34,13 +34,102 @@ # Get the mesh # ------------ +# Use-case: big mesh, split by mat_id and thickness, get meshes for mat_id 1, limited evaluations + +# print(simulation.mesh_info) -> Queries metadata +# ... +# Available properties/labels: "mat_id", "thickness", "eltype", "elshape" +# ... + + +# Mesh object holds on a MeshesContainer, specify splits and necessary evaluation defined at query +# Specific sub-mesh queries on Mesh by property + +# Proposal 1: Mesh wraps a MeshesContainer - simulation.mesh getter +# meshes : Meshes = simulation.mesh(split_by="mat_id") +# meshes : Meshes = simulation.mesh(split_by=["mat_id", "thickness"]) + +# Get mesh split by thickness and mat_id but only for mat_id in 1, 2, 3 +# mesh : Mesh = simulation.mesh(mat_id=[1, 2, 3], split_by=["mat_id", "thickness"]) +# print(mesh) +# mesh : Mesh = simulation.mesh(mat_id=[1, 2, 3], thickness=) + +# Pros: +# - Not lazy but very targeted evaluations -> same logic as result queries +# - simulation.mesh gets you a mesh/holder on meshes +# Cons: +# - simulation.mesh() by default would query everything as a single mesh? +# - not a property + + +# si + + +# simulation.mesh -> Mesh +# simulation.mesh.nodes_ids + + +# Proposal 2: Mesh wraps a MeshesContainer - no simulation.mesh, only query methods +# mesh_info : str = simulation.mesh_info + +# Get the whole mesh +# mesh : Mesh = simulation.mesh (property) +# meshes : Meshes = mesh.split_by() + +# Get the mesh split by material ID +# meshes : Meshes = simulation.split_mesh_by("mat_id", "thickness") <-- + + +# Get the mesh split by material ID but only for mat_id 1 and 2 and thickness 2 +# meshes : Meshes = simulation.split_mesh_by(labels={"mat_id": [1, 2], "elshape": }) <-- +# mesh = meshes.select(mat_id=1) +# simulation.create_elemental_named_selection() -> Selection +# simulation.create_named_selection(element_ids=[,2,,4], name="my_ns") -> Selection +# simulation.split_mesh_by({"named_selection"="my_ns") +# simulation.save_hfd5() +# mesh.name = "mat_id 1" + + +# print(meshes) +# """ +# mesh1: {mat_id=1, thickness=2} +# mesh1: {mat_id=2, thickness=2} +# """ + + +# print(meshes) +# """ +# mesh0: {mat_id=1} +# mesh1: {mat_id=2} +# """ +# Pros: +# - explicit +# Cons: + + +# Proposal 3: simulation.mesh is a "method" holder, not an object +# meshes : Mesh = simulation.mesh.get_mesh_split_by_property("mat_id", "thickness") +# print(meshes) +# """ +# mesh0: {mat_id=1, thickness=1} +# mesh1: {mat_id=1, thickness=2} +# mesh2: {mat_id=2, thickness=1} +# mesh3: {mat_id=2, thickness=2} +# """ + +# Cons: +# simulation.mesh does not return anything + + mesh = simulation.mesh print(mesh) -# meshes : MeshesContainer = simulation.meshes_with_elements_grouped_by("mat_id") -# meshes : MeshesContainer = simulation.meshes(elements_grouped_by="mat_id") -# meshes : MeshesContainer = simulation.meshes.elements_grouped_by("mat_id") -# meshes : MeshesContainer = simulation.meshes.elements_grouped_by("mat_id") -# meshes : MeshesContainer = simulation.meshes_by_properties("mat_id", "thickness") +# meshes : Meshes = simulation.meshes_with_elements_grouped_by("mat_id") + +# meshes : Meshes = simulation.meshes(elements_grouped_by="mat_id") + +# meshes : Meshes = simulation.meshes.elements_grouped_by("mat_id") + +# meshes : Meshes = simulation.meshes_by_properties("mat_id", "thickness") # print(meshes) # """ # mesh0: {mat_id=1, thickness=1} @@ -48,10 +137,12 @@ # mesh2: {mat_id=2, thickness=1} # mesh3: {mat_id=2, thickness=2} # """ -# mesh = simulation.mesh_by_property(mat_id=1) +# mesh : Mesh = simulation.mesh_by_property(mat_id=1) # mesh_info = simulation.mesh_info -> # mesh = MeshesContainer.mesh_by_id(mat_id=1) # for id, mesh for + + ############################################################################### # Query basic information about the mesh (available) # -------------------------------------- From d3add0e8bd167dff227f0174cedeb8959e3a6a57 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 21 Mar 2023 11:00:17 +0100 Subject: [PATCH 04/99] Should expose MeshSelectionManager? --- examples/01-Detailed-Examples/05-mesh-exploration.py | 4 ++++ src/ansys/dpf/post/simulation.py | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/examples/01-Detailed-Examples/05-mesh-exploration.py b/examples/01-Detailed-Examples/05-mesh-exploration.py index c33a83525..b8ab9cf65 100644 --- a/examples/01-Detailed-Examples/05-mesh-exploration.py +++ b/examples/01-Detailed-Examples/05-mesh-exploration.py @@ -30,6 +30,10 @@ # print the simulation to get an overview of what's available print(simulation) +mesh_info = simulation.mesh_info # TODO: expose MeshSelectionManager? +print(mesh_info) + +exit() ############################################################################### # Get the mesh # ------------ diff --git a/src/ansys/dpf/post/simulation.py b/src/ansys/dpf/post/simulation.py index e0f855f5c..6a4616194 100644 --- a/src/ansys/dpf/post/simulation.py +++ b/src/ansys/dpf/post/simulation.py @@ -89,6 +89,18 @@ def release_streams(self): """Release the streams to data files if any is active.""" self._model.metadata.release_streams() + @property + def mesh_info(self): + """Return available mesh information.""" + mesh_selection_manager_provider_op = ( + dpf.operators.metadata.mesh_selection_manager_provider( + data_sources=self._data_sources + ) + ) + outputs = mesh_selection_manager_provider_op.outputs + # print(dir(outputs)) + return outputs.mesh_selection_manager + @property def results(self) -> List[str]: r"""Available results. From d1350761479fd645c2b7f19357ad63c2febe1832 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 21 Mar 2023 15:35:15 +0100 Subject: [PATCH 05/99] Typo --- src/ansys/dpf/post/mesh.py | 2 +- src/ansys/dpf/post/simulation.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index cda75cc66..69a56ae1a 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -10,7 +10,7 @@ class Mesh: - """Exposes the mesh of the solution.""" + """Exposes the complete mesh of the simulation.""" def __init__(self, meshed_region: MeshedRegion): """Initialize this class.""" diff --git a/src/ansys/dpf/post/simulation.py b/src/ansys/dpf/post/simulation.py index 6a4616194..a21e2fcdd 100644 --- a/src/ansys/dpf/post/simulation.py +++ b/src/ansys/dpf/post/simulation.py @@ -27,6 +27,7 @@ SetIndex, ) from ansys.dpf.post.mesh import Mesh +from ansys.dpf.post.meshes import Meshes from ansys.dpf.post.selection import Selection component_label_to_index = { @@ -343,6 +344,34 @@ def units(self): """Returns the current time/frequency and distance units used.""" return self._units + def split_mesh_by(self, labels: dict) -> Meshes: + """Splits the simulation Mesh according to labels and returns it as Meshes.""" + meshes = [self.mesh._core_object] + label_spaces = [] + for label in labels.keys(): + new_meshes = [] + new_label_spaces = [] + for mesh in meshes: + split_op = dpf.operators.mesh.split_mesh( + mesh=mesh, + property=label, + ) + new_meshes_container = split_op.outputs.meshes() + for i, m in enumerate(new_meshes_container): + new_meshes.append(m) + new_label_spaces.append(new_meshes_container.get_label_space(i)) + meshes = new_meshes + label_spaces = new_label_spaces + + # Reconstruct the meshes_container + meshes_container = dpf.MeshesContainer() + for label_space in label_spaces: + for key in label_space.keys(): + meshes_container.add_label(label=key) + for i, mesh in enumerate(meshes): + meshes_container.add_mesh(mesh=mesh, label_space=label_spaces[i]) + return Meshes(meshes_container=meshes_container) + def __str__(self): """Get the string representation of this class.""" txt = ( From dfb698358e21eda6f32f1b05c641aef8a3a0ee00 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 21 Mar 2023 15:56:41 +0100 Subject: [PATCH 06/99] Add meshes.py --- src/ansys/dpf/post/meshes.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/ansys/dpf/post/meshes.py diff --git a/src/ansys/dpf/post/meshes.py b/src/ansys/dpf/post/meshes.py new file mode 100644 index 000000000..2a61f81e1 --- /dev/null +++ b/src/ansys/dpf/post/meshes.py @@ -0,0 +1,27 @@ +"""Module containing the ``Meshes`` class. + +Meshes +------ + +""" +from __future__ import annotations + +from ansys.dpf.core import MeshesContainer + +from ansys.dpf.post.mesh import Mesh + + +class Meshes: + """Container to hold and interact with a split mesh.""" + + def __init__(self, meshes_container: MeshesContainer): + """Initialize this class.""" + self._core_object = meshes_container + + def __getitem__(self, item) -> Mesh: + """Select a specific mesh based on its position in the container.""" + return Mesh(meshed_region=self._core_object[item]) + + def __str__(self): + """String representation of this class.""" + return str(self._core_object) From 87195ea0627ac86812d70eb06de9f6da95ac0228 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 21 Mar 2023 15:57:29 +0100 Subject: [PATCH 07/99] Add Meshes.select() --- src/ansys/dpf/post/meshes.py | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/ansys/dpf/post/meshes.py b/src/ansys/dpf/post/meshes.py index 2a61f81e1..14630c50f 100644 --- a/src/ansys/dpf/post/meshes.py +++ b/src/ansys/dpf/post/meshes.py @@ -6,6 +6,9 @@ """ from __future__ import annotations +from typing import Union + +import ansys.dpf.core as dpf from ansys.dpf.core import MeshesContainer from ansys.dpf.post.mesh import Mesh @@ -25,3 +28,43 @@ def __getitem__(self, item) -> Mesh: def __str__(self): """String representation of this class.""" return str(self._core_object) + + def select(self, **kwargs) -> Union[Mesh, Meshes, None]: + """Select a specific mesh based on a combination of property values. + + Parameters + ---------- + **kwargs: + A dictionary of property names with associated values to select. + If values are not defined for a property, all available values are selected. + + Returns + ------- + A Mesh when the selection results in a unique mesh, + or a Meshes when several are selected, + or None when the criteria result in no mesh. + + """ + initial_labels = self._core_object.labels + # Filter labels + label_space = {} + for key in kwargs.keys(): + if key in initial_labels: + label_space[key] = kwargs[key] + selected_meshes = self._core_object.get_meshes(label_space=label_space) + if len(selected_meshes) == 1: + return Mesh(meshed_region=selected_meshes[0]) + elif len(selected_meshes) == 0: + return None + else: + meshes_container = dpf.MeshesContainer() + for label in initial_labels: + meshes_container.add_label(label=label) + for i, mesh in enumerate(self._core_object): + if mesh in selected_meshes: + new_label_space = self._core_object.get_label_space(i) + meshes_container.add_mesh( + label_space=new_label_space, + mesh=mesh, + ) + return Meshes(meshes_container=meshes_container) From 0fe0cdd5e4feeddd072d1ae97a7dca59d087d4a4 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 21 Mar 2023 16:44:43 +0100 Subject: [PATCH 08/99] Simulation.split_mesh_by_properties() - version using scopings --- src/ansys/dpf/post/simulation.py | 48 +++++++++++++++----------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/ansys/dpf/post/simulation.py b/src/ansys/dpf/post/simulation.py index a21e2fcdd..a853b2061 100644 --- a/src/ansys/dpf/post/simulation.py +++ b/src/ansys/dpf/post/simulation.py @@ -344,32 +344,30 @@ def units(self): """Returns the current time/frequency and distance units used.""" return self._units - def split_mesh_by(self, labels: dict) -> Meshes: - """Splits the simulation Mesh according to labels and returns it as Meshes.""" - meshes = [self.mesh._core_object] - label_spaces = [] - for label in labels.keys(): - new_meshes = [] - new_label_spaces = [] - for mesh in meshes: - split_op = dpf.operators.mesh.split_mesh( - mesh=mesh, - property=label, - ) - new_meshes_container = split_op.outputs.meshes() - for i, m in enumerate(new_meshes_container): - new_meshes.append(m) - new_label_spaces.append(new_meshes_container.get_label_space(i)) - meshes = new_meshes - label_spaces = new_label_spaces - - # Reconstruct the meshes_container + def split_mesh_by_properties(self, properties: dict = None) -> Meshes: + """Splits the simulation Mesh according to properties and returns it as Meshes.""" + split_op = dpf.operators.scoping.split_on_property_type( + mesh=self._model.metadata.mesh_provider.outputs.mesh, + requested_location=dpf.locations.elemental, + ) + if properties is not None: + for i, label in enumerate(properties.keys()): + split_op.connect(13 + i, label) + scopings_container = split_op.outputs.mesh_scoping() + # meshes = [] meshes_container = dpf.MeshesContainer() - for label_space in label_spaces: - for key in label_space.keys(): - meshes_container.add_label(label=key) - for i, mesh in enumerate(meshes): - meshes_container.add_mesh(mesh=mesh, label_space=label_spaces[i]) + for label in scopings_container.labels: + meshes_container.add_label(label) + for i, scoping in enumerate(scopings_container): + mesh_from_scoping = dpf.operators.mesh.from_scoping( + scoping=scoping, + mesh=self._model.metadata.mesh_provider.outputs.mesh, + ) + # meshes.append(mesh_from_scoping.outputs.mesh()) + meshes_container.add_mesh( + mesh=mesh_from_scoping.outputs.mesh(), + label_space=scopings_container.get_label_space(i), + ) return Meshes(meshes_container=meshes_container) def __str__(self): From b7eb6c8ff7da2b52293caede7c519226e4767190 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Wed, 22 Mar 2023 15:22:54 +0100 Subject: [PATCH 09/99] WIP --- examples/01-Detailed-Examples/05-mesh-exploration.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/examples/01-Detailed-Examples/05-mesh-exploration.py b/examples/01-Detailed-Examples/05-mesh-exploration.py index b8ab9cf65..a870cd894 100644 --- a/examples/01-Detailed-Examples/05-mesh-exploration.py +++ b/examples/01-Detailed-Examples/05-mesh-exploration.py @@ -24,16 +24,16 @@ # ``"C:/Users/user/my_result.rst"`` on Windows or ``"/home/user/my_result.rst"`` # on Linux. -example_path = examples.download_crankshaft() +example_path = examples.download_all_kinds_of_complexity() simulation = post.StaticMechanicalSimulation(example_path) # print the simulation to get an overview of what's available print(simulation) mesh_info = simulation.mesh_info # TODO: expose MeshSelectionManager? -print(mesh_info) +# print(mesh_info) -exit() +# exit() ############################################################################### # Get the mesh # ------------ @@ -86,7 +86,13 @@ # Get the mesh split by material ID but only for mat_id 1 and 2 and thickness 2 # meshes : Meshes = simulation.split_mesh_by(labels={"mat_id": [1, 2], "elshape": }) <-- +# meshes = simulation.split_mesh_by(labels={"mat_id": [1, 2]}) +meshes = simulation.split_mesh_by_properties(properties={"mat_id": [1, 2]}) +print(meshes._core_object) # mesh = meshes.select(mat_id=1) +# print(mesh) +exit() + # simulation.create_elemental_named_selection() -> Selection # simulation.create_named_selection(element_ids=[,2,,4], name="my_ns") -> Selection # simulation.split_mesh_by({"named_selection"="my_ns") From f575069bd86842b5091b91e65119a08d33f73922 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Thu, 23 Mar 2023 19:03:12 +0100 Subject: [PATCH 10/99] Add Mesh.plot() --- src/ansys/dpf/post/mesh.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index 69a56ae1a..0aae475ef 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -39,3 +39,7 @@ def element_ids(self) -> List[int]: def _core_object(self): """Returns the underlying PyDPF-Core class:`ansys.dpf.core.MeshedRegion` object.""" return self._meshed_region + + def plot(self, **kwargs): + """Plots the Mesh.""" + return self._core_object.plot(**kwargs) From 3b0edf0fa5191a22f19a2d3a5720c103c1d95eb0 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Thu, 23 Mar 2023 19:04:06 +0100 Subject: [PATCH 11/99] Add Meshes.plot() --- src/ansys/dpf/post/meshes.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ansys/dpf/post/meshes.py b/src/ansys/dpf/post/meshes.py index 14630c50f..463835207 100644 --- a/src/ansys/dpf/post/meshes.py +++ b/src/ansys/dpf/post/meshes.py @@ -68,3 +68,12 @@ def select(self, **kwargs) -> Union[Mesh, Meshes, None]: mesh=mesh, ) return Meshes(meshes_container=meshes_container) + + def plot(self, **kwargs): + """Plots the Meshes.""" + from ansys.dpf.core.plotter import DpfPlotter + + plt = DpfPlotter(**kwargs) + for mesh in self._core_object: + plt.add_mesh(meshed_region=mesh, **kwargs) + return plt.show_figure(**kwargs) From d1ccfadbc9722195e7e5c4a0167c7d3e790799c9 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Thu, 23 Mar 2023 19:04:59 +0100 Subject: [PATCH 12/99] Add Meshes.__get_item__ by dictionary of {property: value} --- src/ansys/dpf/post/meshes.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ansys/dpf/post/meshes.py b/src/ansys/dpf/post/meshes.py index 463835207..fdeea4fc7 100644 --- a/src/ansys/dpf/post/meshes.py +++ b/src/ansys/dpf/post/meshes.py @@ -21,9 +21,15 @@ def __init__(self, meshes_container: MeshesContainer): """Initialize this class.""" self._core_object = meshes_container - def __getitem__(self, item) -> Mesh: + def __getitem__(self, item: Union[int, dict]) -> Mesh: """Select a specific mesh based on its position in the container.""" - return Mesh(meshed_region=self._core_object[item]) + if isinstance(item, (int, dict)): + return Mesh(meshed_region=self._core_object.get_mesh(item)) + else: + ValueError( + "Access to a specific Mesh of a Meshes requires an index (int) " + "or a combination of labels (dict)." + ) def __str__(self): """String representation of this class.""" From 500d7112e4e313da1a06ab47d7363ef687e8713b Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Thu, 23 Mar 2023 19:06:03 +0100 Subject: [PATCH 13/99] Working POC with split by properties, and selection by properties, with plot --- examples/01-Detailed-Examples/05-mesh-exploration.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/01-Detailed-Examples/05-mesh-exploration.py b/examples/01-Detailed-Examples/05-mesh-exploration.py index a870cd894..f852cc0cd 100644 --- a/examples/01-Detailed-Examples/05-mesh-exploration.py +++ b/examples/01-Detailed-Examples/05-mesh-exploration.py @@ -87,8 +87,17 @@ # Get the mesh split by material ID but only for mat_id 1 and 2 and thickness 2 # meshes : Meshes = simulation.split_mesh_by(labels={"mat_id": [1, 2], "elshape": }) <-- # meshes = simulation.split_mesh_by(labels={"mat_id": [1, 2]}) -meshes = simulation.split_mesh_by_properties(properties={"mat_id": [1, 2]}) +meshes = simulation.split_mesh_by_properties( + properties={"mat": [1, 2], "elshape": [1, 2]} +) print(meshes._core_object) +print(meshes._core_object.labels) +print(meshes._core_object.get_available_ids_for_label("mat")) +print(meshes._core_object.get_available_ids_for_label("elshape")) +meshes.plot() +meshes[1].plot() + +meshes[{"mat": 5, "elshape": 0}].plot() # mesh = meshes.select(mat_id=1) # print(mesh) exit() From 72accfc017706107b9c7b89c8910f20931793bab Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Thu, 23 Mar 2023 19:08:33 +0100 Subject: [PATCH 14/99] Split mesh by list of properties --- examples/01-Detailed-Examples/05-mesh-exploration.py | 4 +--- src/ansys/dpf/post/simulation.py | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/examples/01-Detailed-Examples/05-mesh-exploration.py b/examples/01-Detailed-Examples/05-mesh-exploration.py index f852cc0cd..a61a40870 100644 --- a/examples/01-Detailed-Examples/05-mesh-exploration.py +++ b/examples/01-Detailed-Examples/05-mesh-exploration.py @@ -87,9 +87,7 @@ # Get the mesh split by material ID but only for mat_id 1 and 2 and thickness 2 # meshes : Meshes = simulation.split_mesh_by(labels={"mat_id": [1, 2], "elshape": }) <-- # meshes = simulation.split_mesh_by(labels={"mat_id": [1, 2]}) -meshes = simulation.split_mesh_by_properties( - properties={"mat": [1, 2], "elshape": [1, 2]} -) +meshes = simulation.split_mesh_by_properties(properties=["mat", "elshape"]) print(meshes._core_object) print(meshes._core_object.labels) print(meshes._core_object.get_available_ids_for_label("mat")) diff --git a/src/ansys/dpf/post/simulation.py b/src/ansys/dpf/post/simulation.py index a853b2061..961effc71 100644 --- a/src/ansys/dpf/post/simulation.py +++ b/src/ansys/dpf/post/simulation.py @@ -344,15 +344,15 @@ def units(self): """Returns the current time/frequency and distance units used.""" return self._units - def split_mesh_by_properties(self, properties: dict = None) -> Meshes: + def split_mesh_by_properties(self, properties: List[str] = None) -> Meshes: """Splits the simulation Mesh according to properties and returns it as Meshes.""" split_op = dpf.operators.scoping.split_on_property_type( mesh=self._model.metadata.mesh_provider.outputs.mesh, requested_location=dpf.locations.elemental, ) if properties is not None: - for i, label in enumerate(properties.keys()): - split_op.connect(13 + i, label) + for i, prop in enumerate(properties): + split_op.connect(13 + i, prop) scopings_container = split_op.outputs.mesh_scoping() # meshes = [] meshes_container = dpf.MeshesContainer() From 94d9df0c3c3b2c45e6229fa709d6b76f07a96061 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 27 Mar 2023 14:33:33 +0200 Subject: [PATCH 15/99] Add test_meshes.py --- tests/test_meshes.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/test_meshes.py diff --git a/tests/test_meshes.py b/tests/test_meshes.py new file mode 100644 index 000000000..e69de29bb From 1396828e8d7668b4db84c8e73086c78545c542fc Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 27 Mar 2023 16:09:15 +0200 Subject: [PATCH 16/99] Use "import ansys.dpf.core as dpf" in test_mesh.py --- tests/test_mesh.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_mesh.py b/tests/test_mesh.py index aa4d6852e..d6925b7c9 100644 --- a/tests/test_mesh.py +++ b/tests/test_mesh.py @@ -1,4 +1,4 @@ -import ansys.dpf.core as core +import ansys.dpf.core as dpf import numpy as np from pytest import fixture @@ -12,7 +12,7 @@ def mesh(static_rst): def test_mesh_core_object(mesh): - assert isinstance(mesh._core_object, core.MeshedRegion) + assert isinstance(mesh._core_object, dpf.MeshedRegion) assert mesh._core_object.nodes.n_nodes == 81 From 955ef0725d307ac08d0ddf5d45cc10c59413de01 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 27 Mar 2023 16:19:53 +0200 Subject: [PATCH 17/99] Add test_simulation_split_mesh_by_properties --- tests/test_simulation.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_simulation.py b/tests/test_simulation.py index f955f5f00..58ed8a93a 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -8,6 +8,7 @@ from ansys.dpf import post from ansys.dpf.post.common import AvailableSimulationTypes from ansys.dpf.post.index import ref_labels +from ansys.dpf.post.meshes import Meshes @fixture @@ -74,6 +75,12 @@ def test_simulation_plot(static_simulation): static_simulation.plot() +def test_simulation_split_mesh_by_properties(allkindofcomplexity): + simulation = post.StaticMechanicalSimulation(allkindofcomplexity) + meshes = simulation.split_mesh_by_properties(properties=["mat", "elshape"]) + assert isinstance(meshes, Meshes) + + class TestStaticMechanicalSimulation: def test_times_argument(self, static_simulation): _ = static_simulation.displacement(times=1) From ce7fa1e0653053aac0fc79ee2fc75300088f8eb8 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 27 Mar 2023 16:24:31 +0200 Subject: [PATCH 18/99] Add Mesh and Meshes imports at module init --- src/ansys/dpf/post/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ansys/dpf/post/__init__.py b/src/ansys/dpf/post/__init__.py index 20a2a0042..e7a16415c 100644 --- a/src/ansys/dpf/post/__init__.py +++ b/src/ansys/dpf/post/__init__.py @@ -32,6 +32,8 @@ from ansys.dpf.post.harmonic_mechanical_simulation import ( # noqa: F401 HarmonicMechanicalSimulation, ) +from ansys.dpf.post.mesh import Mesh # noqa: F401 +from ansys.dpf.post.meshes import Meshes # noqa: F401 from ansys.dpf.post.misc import Report from ansys.dpf.post.modal_mechanical_simulation import ( # noqa: F401 ModalMechanicalSimulation, From e746560ac417131969474451a38ba04624230a80 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 27 Mar 2023 16:38:23 +0200 Subject: [PATCH 19/99] Add Meshes.__len__() --- src/ansys/dpf/post/meshes.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ansys/dpf/post/meshes.py b/src/ansys/dpf/post/meshes.py index fdeea4fc7..e9049b8df 100644 --- a/src/ansys/dpf/post/meshes.py +++ b/src/ansys/dpf/post/meshes.py @@ -26,7 +26,7 @@ def __getitem__(self, item: Union[int, dict]) -> Mesh: if isinstance(item, (int, dict)): return Mesh(meshed_region=self._core_object.get_mesh(item)) else: - ValueError( + raise ValueError( "Access to a specific Mesh of a Meshes requires an index (int) " "or a combination of labels (dict)." ) @@ -35,6 +35,10 @@ def __str__(self): """String representation of this class.""" return str(self._core_object) + def __len__(self): + """Return the length of the Meshes.""" + return len(self._core_object) + def select(self, **kwargs) -> Union[Mesh, Meshes, None]: """Select a specific mesh based on a combination of property values. From 5fac5d7535881e05349dbc2896d67e70edd4ce44 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 27 Mar 2023 17:25:48 +0200 Subject: [PATCH 20/99] Fix and test Meshes.select() --- src/ansys/dpf/post/meshes.py | 41 +++++++++++++++++++++------ tests/test_meshes.py | 55 ++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 8 deletions(-) diff --git a/src/ansys/dpf/post/meshes.py b/src/ansys/dpf/post/meshes.py index e9049b8df..19cb8c4cc 100644 --- a/src/ansys/dpf/post/meshes.py +++ b/src/ansys/dpf/post/meshes.py @@ -6,6 +6,7 @@ """ from __future__ import annotations +import itertools from typing import Union import ansys.dpf.core as dpf @@ -61,7 +62,33 @@ def select(self, **kwargs) -> Union[Mesh, Meshes, None]: for key in kwargs.keys(): if key in initial_labels: label_space[key] = kwargs[key] - selected_meshes = self._core_object.get_meshes(label_space=label_space) + # selected_meshes = self._core_object.get_meshes(label_space=label_space) + # Create label_spaces to select + label_values_to_select = {} + for label in initial_labels: + if label in list(label_space.keys()): + values = label_space[label] + if not isinstance(values, list): + values = [values] + label_values_to_select[label] = values + else: + label_values_to_select[ + label + ] = self._core_object.get_available_ids_for_label(label) + + combinations = itertools.product( + *[values for values in label_values_to_select.values()] + ) + selected_meshes = [] + label_spaces_to_select = [dict(zip(initial_labels, p)) for p in combinations] + selected_meshes_label_spaces = [] + for p in label_spaces_to_select: + m = self._core_object.get_mesh(p) + if m is None: + continue + selected_meshes.append(m) + selected_meshes_label_spaces.append(p) + if len(selected_meshes) == 1: return Mesh(meshed_region=selected_meshes[0]) elif len(selected_meshes) == 0: @@ -70,13 +97,11 @@ def select(self, **kwargs) -> Union[Mesh, Meshes, None]: meshes_container = dpf.MeshesContainer() for label in initial_labels: meshes_container.add_label(label=label) - for i, mesh in enumerate(self._core_object): - if mesh in selected_meshes: - new_label_space = self._core_object.get_label_space(i) - meshes_container.add_mesh( - label_space=new_label_space, - mesh=mesh, - ) + for i, selected in enumerate(selected_meshes): + meshes_container.add_mesh( + label_space=selected_meshes_label_spaces[i], + mesh=selected, + ) return Meshes(meshes_container=meshes_container) def plot(self, **kwargs): diff --git a/tests/test_meshes.py b/tests/test_meshes.py index e69de29bb..a4fe948a6 100644 --- a/tests/test_meshes.py +++ b/tests/test_meshes.py @@ -0,0 +1,55 @@ +import ansys.dpf.core as dpf +import pytest +from pytest import fixture + +from ansys.dpf import post +from ansys.dpf.post.static_mechanical_simulation import StaticMechanicalSimulation + + +@fixture +def meshes(allkindofcomplexity): + simulation = StaticMechanicalSimulation(allkindofcomplexity) + return simulation.split_mesh_by_properties(properties=["mat", "elshape"]) + + +def test_meshes_core_object(meshes): + assert isinstance(meshes._core_object, dpf.MeshesContainer) + assert ( + "elshape" in meshes._core_object.labels and "mat" in meshes._core_object.labels + ) + + +def test_meshes_str(meshes): + assert str(meshes) == str(meshes._core_object) + + +def test_meshes_get_item(meshes): + with pytest.raises( + ValueError, match="Access to a specific Mesh of a Meshes requires" + ): + meshes["test"] + mesh1 = meshes[1] + assert isinstance(mesh1, post.Mesh) + assert len(mesh1.node_ids) == 240 + mesh2 = meshes[{"mat": 1, "elshape": 0}] + assert isinstance(mesh2, post.Mesh) + assert len(mesh2.node_ids) == 240 + + +def test_meshes_plot(meshes): + _ = meshes.plot() + + +def test_meshes_select(meshes): + mesh = meshes.select(mat=1, elshape=0) + assert isinstance(mesh, post.Mesh) + assert len(mesh.node_ids) == 240 + mesh_none = meshes.select(mat=22, elshape=42) + assert mesh_none is None + meshes_mat = meshes.select(mat=5) + assert isinstance(meshes_mat, post.Mesh) + assert len(meshes_mat.node_ids) == 248 + meshes_mat = meshes.select(mat=1) + print(meshes_mat._core_object) + assert isinstance(meshes_mat, post.Meshes) + assert len(meshes_mat) == 2 From 2caad5b3adca428898a6bd5a40fe7d1924fd8a05 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 27 Mar 2023 17:42:27 +0200 Subject: [PATCH 21/99] Add selection by values in Simulation.split_mesh_by_properties() --- src/ansys/dpf/post/simulation.py | 32 ++++++++++++++++++++++++++------ tests/test_simulation.py | 17 +++++++++++++++++ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/ansys/dpf/post/simulation.py b/src/ansys/dpf/post/simulation.py index 961effc71..3f16f8ebb 100644 --- a/src/ansys/dpf/post/simulation.py +++ b/src/ansys/dpf/post/simulation.py @@ -344,15 +344,31 @@ def units(self): """Returns the current time/frequency and distance units used.""" return self._units - def split_mesh_by_properties(self, properties: List[str] = None) -> Meshes: - """Splits the simulation Mesh according to properties and returns it as Meshes.""" + def split_mesh_by_properties( + self, + properties: List[str], + values: List[Union[int, List[int]]] = None, + ) -> Meshes: + """Splits the simulation Mesh according to properties and returns it as Meshes. + + Parameters + ---------- + properties: + Labels of properties to split the global mesh by. + values: + Associated label values for which to return the resulting meshes. + + Returns + ------- + A Meshes entity with resulting meshes. + + """ split_op = dpf.operators.scoping.split_on_property_type( mesh=self._model.metadata.mesh_provider.outputs.mesh, requested_location=dpf.locations.elemental, ) - if properties is not None: - for i, prop in enumerate(properties): - split_op.connect(13 + i, prop) + for i, prop in enumerate(properties): + split_op.connect(13 + i, prop) scopings_container = split_op.outputs.mesh_scoping() # meshes = [] meshes_container = dpf.MeshesContainer() @@ -368,7 +384,11 @@ def split_mesh_by_properties(self, properties: List[str] = None) -> Meshes: mesh=mesh_from_scoping.outputs.mesh(), label_space=scopings_container.get_label_space(i), ) - return Meshes(meshes_container=meshes_container) + meshes = Meshes(meshes_container=meshes_container) + if values is None: + return meshes + else: + return meshes.select(**dict(zip(properties, values))) def __str__(self): """Get the string representation of this class.""" diff --git a/tests/test_simulation.py b/tests/test_simulation.py index 58ed8a93a..5207e4b38 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -79,6 +79,23 @@ def test_simulation_split_mesh_by_properties(allkindofcomplexity): simulation = post.StaticMechanicalSimulation(allkindofcomplexity) meshes = simulation.split_mesh_by_properties(properties=["mat", "elshape"]) assert isinstance(meshes, Meshes) + assert len(meshes) == 16 + meshes = simulation.split_mesh_by_properties( + properties=["mat", "elshape"], + values=[1, [0, 1]], + ) + assert isinstance(meshes, Meshes) + assert len(meshes) == 2 + meshes = simulation.split_mesh_by_properties( + properties=["mat", "elshape"], + values=[1, [0, 2]], + ) + assert isinstance(meshes, post.Mesh) + meshes = simulation.split_mesh_by_properties( + properties=["mat", "elshape"], + values=[22, [0, 2]], + ) + assert meshes is None class TestStaticMechanicalSimulation: From 07e37fd935a267e32ed7ac9fb8eaaadf79926797 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 27 Mar 2023 18:57:59 +0200 Subject: [PATCH 22/99] Update 05-mesh-exploration.py --- .../05-mesh-exploration.py | 284 ++++++------------ 1 file changed, 96 insertions(+), 188 deletions(-) diff --git a/examples/01-Detailed-Examples/05-mesh-exploration.py b/examples/01-Detailed-Examples/05-mesh-exploration.py index a61a40870..d8dbff99f 100644 --- a/examples/01-Detailed-Examples/05-mesh-exploration.py +++ b/examples/01-Detailed-Examples/05-mesh-exploration.py @@ -30,75 +30,29 @@ # print the simulation to get an overview of what's available print(simulation) -mesh_info = simulation.mesh_info # TODO: expose MeshSelectionManager? -# print(mesh_info) +# mesh_info = simulation.mesh_info # TODO: expose MeshSelectionManager? +# # print(mesh_info) -# exit() ############################################################################### # Get the mesh # ------------ -# Use-case: big mesh, split by mat_id and thickness, get meshes for mat_id 1, limited evaluations - -# print(simulation.mesh_info) -> Queries metadata -# ... -# Available properties/labels: "mat_id", "thickness", "eltype", "elshape" -# ... - - -# Mesh object holds on a MeshesContainer, specify splits and necessary evaluation defined at query -# Specific sub-mesh queries on Mesh by property - -# Proposal 1: Mesh wraps a MeshesContainer - simulation.mesh getter -# meshes : Meshes = simulation.mesh(split_by="mat_id") -# meshes : Meshes = simulation.mesh(split_by=["mat_id", "thickness"]) - -# Get mesh split by thickness and mat_id but only for mat_id in 1, 2, 3 -# mesh : Mesh = simulation.mesh(mat_id=[1, 2, 3], split_by=["mat_id", "thickness"]) -# print(mesh) -# mesh : Mesh = simulation.mesh(mat_id=[1, 2, 3], thickness=) - -# Pros: -# - Not lazy but very targeted evaluations -> same logic as result queries -# - simulation.mesh gets you a mesh/holder on meshes -# Cons: -# - simulation.mesh() by default would query everything as a single mesh? -# - not a property - - -# si - - -# simulation.mesh -> Mesh -# simulation.mesh.nodes_ids - - -# Proposal 2: Mesh wraps a MeshesContainer - no simulation.mesh, only query methods -# mesh_info : str = simulation.mesh_info - -# Get the whole mesh -# mesh : Mesh = simulation.mesh (property) -# meshes : Meshes = mesh.split_by() - -# Get the mesh split by material ID -# meshes : Meshes = simulation.split_mesh_by("mat_id", "thickness") <-- - - -# Get the mesh split by material ID but only for mat_id 1 and 2 and thickness 2 -# meshes : Meshes = simulation.split_mesh_by(labels={"mat_id": [1, 2], "elshape": }) <-- -# meshes = simulation.split_mesh_by(labels={"mat_id": [1, 2]}) +# Split the global mesh according to mesh properties meshes = simulation.split_mesh_by_properties(properties=["mat", "elshape"]) -print(meshes._core_object) -print(meshes._core_object.labels) -print(meshes._core_object.get_available_ids_for_label("mat")) -print(meshes._core_object.get_available_ids_for_label("elshape")) meshes.plot() + +# Split the global mesh and select meshes for specific property values +meshes = simulation.split_mesh_by_properties( + properties=["mat", "elshape"], + values=[1, [0, 1]], +) +meshes.plot() + +# Select a specific Mesh in the Meshes, by index meshes[1].plot() +# or by property values +meshes[{"mat": 1, "elshape": 0}].plot() -meshes[{"mat": 5, "elshape": 0}].plot() -# mesh = meshes.select(mat_id=1) -# print(mesh) -exit() # simulation.create_elemental_named_selection() -> Selection # simulation.create_named_selection(element_ids=[,2,,4], name="my_ns") -> Selection @@ -106,59 +60,13 @@ # simulation.save_hfd5() # mesh.name = "mat_id 1" - # print(meshes) # """ # mesh1: {mat_id=1, thickness=2} # mesh1: {mat_id=2, thickness=2} # """ - -# print(meshes) -# """ -# mesh0: {mat_id=1} -# mesh1: {mat_id=2} -# """ -# Pros: -# - explicit -# Cons: - - -# Proposal 3: simulation.mesh is a "method" holder, not an object -# meshes : Mesh = simulation.mesh.get_mesh_split_by_property("mat_id", "thickness") -# print(meshes) -# """ -# mesh0: {mat_id=1, thickness=1} -# mesh1: {mat_id=1, thickness=2} -# mesh2: {mat_id=2, thickness=1} -# mesh3: {mat_id=2, thickness=2} -# """ - -# Cons: -# simulation.mesh does not return anything - - mesh = simulation.mesh -print(mesh) -# meshes : Meshes = simulation.meshes_with_elements_grouped_by("mat_id") - -# meshes : Meshes = simulation.meshes(elements_grouped_by="mat_id") - -# meshes : Meshes = simulation.meshes.elements_grouped_by("mat_id") - -# meshes : Meshes = simulation.meshes_by_properties("mat_id", "thickness") -# print(meshes) -# """ -# mesh0: {mat_id=1, thickness=1} -# mesh1: {mat_id=1, thickness=2} -# mesh2: {mat_id=2, thickness=1} -# mesh3: {mat_id=2, thickness=2} -# """ -# mesh : Mesh = simulation.mesh_by_property(mat_id=1) -# mesh_info = simulation.mesh_info -> -# mesh = MeshesContainer.mesh_by_id(mat_id=1) -# for id, mesh for - ############################################################################### # Query basic information about the mesh (available) @@ -175,87 +83,87 @@ # Query basic information about the mesh (available in PyDPF-Core) # -------------------------------------- - -# Number of nodes -n_nodes = mesh._core_object.nodes.n_nodes - -# Number of elements -n_elements = mesh._core_object.elements.n_elements - -# Number of named selections -n_ns = len(named_selections) - -# Unit (get and set) -mesh_unit = mesh._core_object.unit -mesh._core_object.unit = "mm" - -# Get the list of nodes/elements IDs of a given named selection -ns_scoping = mesh._core_object.named_selection(named_selection=named_selections[0]) -ns_ids = ns_scoping.ids - -# Named selection setter -mesh._core_object.set_named_selection_scoping( - named_selection_name=named_selections[0], - scoping=ns_scoping, -) +# +# # Number of nodes +# n_nodes = mesh._core_object.nodes.n_nodes +# +# # Number of elements +# n_elements = mesh._core_object.elements.n_elements +# +# # Number of named selections +# n_ns = len(named_selections) +# +# # Unit (get and set) +# mesh_unit = mesh._core_object.unit +# mesh._core_object.unit = "mm" +# +# # Get the list of nodes/elements IDs of a given named selection +# ns_scoping = mesh._core_object.named_selection(named_selection=named_selections[0]) +# ns_ids = ns_scoping.ids +# +# # Named selection setter +# mesh._core_object.set_named_selection_scoping( +# named_selection_name=named_selections[0], +# scoping=ns_scoping, +# ) # Plot the mesh -plt = mesh._core_object.plot() - -# ####################################### -# Manipulating elements - -# Get an element by ID -element_by_id = mesh._core_object.elements.element_by_id(1) -# Get an element by index -element_by_index = mesh._core_object.elements.element_by_index(0) -# Get the ID of an element -e_id = mesh._core_object.elements.element_by_index(0).id - -# Adding elements to the mesh - -# Get the element types -e_types = mesh._core_object.elements.element_types_field - -# Get the materials -e_materials = mesh._core_object.elements.materials_field - -# Get the elemental connectivity -connectivity = mesh._core_object.elements.connectivities_field - -# ############################################## -# Query information about one particular element - -# Get the nodes of an element -e_nodes = mesh._core_object.elements.element_by_id(1).nodes -# Get the node IDs of an element -e_node_ids = mesh._core_object.elements.element_by_id(1).node_ids -# Get the nodes of an element -e_n_node = mesh._core_object.elements.element_by_id(1).n_nodes - -# Get the type of the element -e_type = mesh._core_object.elements.element_by_id(1).type -# Get the shape of the element -e_shape = mesh._core_object.elements.element_by_id(1).shape -# Get the connectivity of the element -e_connectivity = mesh._core_object.elements.element_by_id(1).connectivity - - -# ################## -# Manipulating nodes - -# Get a node by ID -node_by_id = mesh._core_object.nodes.node_by_id(1) -# Get a node by index -node_by_index = mesh._core_object.nodes.node_by_index(0) - -# Get the coordinates of all nodes -coordinates = mesh._core_object.nodes.coordinates_field - -# ########################################### -# Query information about one particular node - -# Coordinates -coor = mesh._core_object.nodes.node_by_id(1).coordinates -# Nodal connectivity -conn = mesh._core_object.nodes.node_by_id(1).nodal_connectivity +plt = mesh.plot() + +# # ####################################### +# # Manipulating elements +# +# # Get an element by ID +# element_by_id = mesh._core_object.elements.element_by_id(1) +# # Get an element by index +# element_by_index = mesh._core_object.elements.element_by_index(0) +# # Get the ID of an element +# e_id = mesh._core_object.elements.element_by_index(0).id +# +# # Adding elements to the mesh +# +# # Get the element types +# e_types = mesh._core_object.elements.element_types_field +# +# # Get the materials +# e_materials = mesh._core_object.elements.materials_field +# +# # Get the elemental connectivity +# connectivity = mesh._core_object.elements.connectivities_field +# +# # ############################################## +# # Query information about one particular element +# +# # Get the nodes of an element +# e_nodes = mesh._core_object.elements.element_by_id(1).nodes +# # Get the node IDs of an element +# e_node_ids = mesh._core_object.elements.element_by_id(1).node_ids +# # Get the nodes of an element +# e_n_node = mesh._core_object.elements.element_by_id(1).n_nodes +# +# # Get the type of the element +# e_type = mesh._core_object.elements.element_by_id(1).type +# # Get the shape of the element +# e_shape = mesh._core_object.elements.element_by_id(1).shape +# # Get the connectivity of the element +# e_connectivity = mesh._core_object.elements.element_by_id(1).connectivity +# +# +# # ################## +# # Manipulating nodes +# +# # Get a node by ID +# node_by_id = mesh._core_object.nodes.node_by_id(1) +# # Get a node by index +# node_by_index = mesh._core_object.nodes.node_by_index(0) +# +# # Get the coordinates of all nodes +# coordinates = mesh._core_object.nodes.coordinates_field +# +# # ########################################### +# # Query information about one particular node +# +# # Coordinates +# coor = mesh._core_object.nodes.node_by_id(1).coordinates +# # Nodal connectivity +# conn = mesh._core_object.nodes.node_by_id(1).nodal_connectivity From 5f254b7f00b0e34793c043cc220fe726013b1fd2 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 28 Mar 2023 11:32:05 +0200 Subject: [PATCH 23/99] Inherit elemental_properties from core --- src/ansys/dpf/post/common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ansys/dpf/post/common.py b/src/ansys/dpf/post/common.py index a6b31f9f5..59ca7ddb8 100644 --- a/src/ansys/dpf/post/common.py +++ b/src/ansys/dpf/post/common.py @@ -4,6 +4,7 @@ ------ """ +from ansys.dpf.core.common import elemental_properties # noqa: F401 from ansys.dpf.post.harmonic_mechanical_simulation import HarmonicMechanicalSimulation from ansys.dpf.post.modal_mechanical_simulation import ModalMechanicalSimulation From 85957abc631a3a6621a701b4ed027412000ffafd Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 28 Mar 2023 11:34:19 +0200 Subject: [PATCH 24/99] Expose elemental_properties at post level --- src/ansys/dpf/post/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ansys/dpf/post/__init__.py b/src/ansys/dpf/post/__init__.py index e7a16415c..51f26b916 100644 --- a/src/ansys/dpf/post/__init__.py +++ b/src/ansys/dpf/post/__init__.py @@ -27,6 +27,7 @@ from ansys.dpf.post import mesh, selection, tools from ansys.dpf.post.common import Grouping as grouping +from ansys.dpf.post.common import elemental_properties # noqa: F401 from ansys.dpf.post.dataframe import DataFrame # noqa: F401 from ansys.dpf.post.dpf_path import create_path_on_coordinates from ansys.dpf.post.harmonic_mechanical_simulation import ( # noqa: F401 From 37814c6581a08e82650b4f564f45f08a183dcb3e Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 28 Mar 2023 11:49:46 +0200 Subject: [PATCH 25/99] Simulation.split_by_property allow dict of properties and values, improve docstring and typehinting, and use elemental_properties enum --- src/ansys/dpf/post/simulation.py | 36 ++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/ansys/dpf/post/simulation.py b/src/ansys/dpf/post/simulation.py index 3f16f8ebb..8dd54a66f 100644 --- a/src/ansys/dpf/post/simulation.py +++ b/src/ansys/dpf/post/simulation.py @@ -8,7 +8,7 @@ from enum import Enum from os import PathLike import re -from typing import List, Tuple, Union +from typing import Dict, List, Tuple, Union import warnings import ansys.dpf.core as dpf @@ -16,6 +16,7 @@ from ansys.dpf.core.plotter import DpfPlotter import numpy as np +from ansys.dpf import post from ansys.dpf.post import locations from ansys.dpf.post.dataframe import DataFrame from ansys.dpf.post.index import ( @@ -346,27 +347,50 @@ def units(self): def split_mesh_by_properties( self, - properties: List[str], - values: List[Union[int, List[int]]] = None, + properties: Union[ + List[post.elemental_properties], + Dict[post.elemental_properties, Union[int, List[int]]], + ], ) -> Meshes: """Splits the simulation Mesh according to properties and returns it as Meshes. Parameters ---------- properties: - Labels of properties to split the global mesh by. - values: - Associated label values for which to return the resulting meshes. + Elemental properties to split the global mesh by. Returns all meshes if a list, + or returns only meshes for certain elemental property values if a dict with + elemental properties labels with associated value or list of values. Returns ------- A Meshes entity with resulting meshes. + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post import examples + >>> example_path = examples.download_all_kinds_of_complexity() + >>> simulation = post.StaticMechanicalSimulation(example_path) + >>> # Split by elemental properties and get all resulting meshes + >>> meshes_split = simulation.split_mesh_by_properties( + >>> properties=[post.elemental_properties.material, + >>> post.elemental_properties.element_shape] + >>> ) + >>> # Split by elemental properties and only get meshes for certain property values + >>> # Here: split by material and shape, return only for material 1 and shapes 0 and 1 + >>> meshes_filtered = simulation.split_mesh_by_properties( + >>> properties={post.elemental_properties.material: 1, + >>> post.elemental_properties.element_shape: [0, 1]} + >>> ) """ split_op = dpf.operators.scoping.split_on_property_type( mesh=self._model.metadata.mesh_provider.outputs.mesh, requested_location=dpf.locations.elemental, ) + values = None + if isinstance(properties, dict): + values = properties.values() + properties = list(properties.keys()) for i, prop in enumerate(properties): split_op.connect(13 + i, prop) scopings_container = split_op.outputs.mesh_scoping() From f0a516db2aa8e20e59233ce32905172498fbab18 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 28 Mar 2023 11:54:32 +0200 Subject: [PATCH 26/99] Update test_simulation_split_by_properties accordingly --- tests/test_simulation.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/tests/test_simulation.py b/tests/test_simulation.py index 5207e4b38..3a77f9963 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -77,23 +77,34 @@ def test_simulation_plot(static_simulation): def test_simulation_split_mesh_by_properties(allkindofcomplexity): simulation = post.StaticMechanicalSimulation(allkindofcomplexity) - meshes = simulation.split_mesh_by_properties(properties=["mat", "elshape"]) + meshes = simulation.split_mesh_by_properties( + properties=[ + post.elemental_properties.material, + post.elemental_properties.element_shape, + ] + ) assert isinstance(meshes, Meshes) assert len(meshes) == 16 meshes = simulation.split_mesh_by_properties( - properties=["mat", "elshape"], - values=[1, [0, 1]], + properties={ + post.elemental_properties.material: 1, + post.elemental_properties.element_shape: [0, 1], + } ) assert isinstance(meshes, Meshes) assert len(meshes) == 2 meshes = simulation.split_mesh_by_properties( - properties=["mat", "elshape"], - values=[1, [0, 2]], + properties={ + post.elemental_properties.material: 1, + post.elemental_properties.element_shape: [0, 2], + } ) assert isinstance(meshes, post.Mesh) meshes = simulation.split_mesh_by_properties( - properties=["mat", "elshape"], - values=[22, [0, 2]], + properties={ + post.elemental_properties.material: 22, + post.elemental_properties.element_shape: [0, 2], + } ) assert meshes is None From 50745c6b93e79668ea63873d095f12df78085755 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 28 Mar 2023 11:58:44 +0200 Subject: [PATCH 27/99] Remove expose elemental_properties at post level due to circular imports --- src/ansys/dpf/post/__init__.py | 1 - src/ansys/dpf/post/simulation.py | 15 ++++++++------- tests/test_simulation.py | 18 +++++++++--------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/ansys/dpf/post/__init__.py b/src/ansys/dpf/post/__init__.py index 51f26b916..e7a16415c 100644 --- a/src/ansys/dpf/post/__init__.py +++ b/src/ansys/dpf/post/__init__.py @@ -27,7 +27,6 @@ from ansys.dpf.post import mesh, selection, tools from ansys.dpf.post.common import Grouping as grouping -from ansys.dpf.post.common import elemental_properties # noqa: F401 from ansys.dpf.post.dataframe import DataFrame # noqa: F401 from ansys.dpf.post.dpf_path import create_path_on_coordinates from ansys.dpf.post.harmonic_mechanical_simulation import ( # noqa: F401 diff --git a/src/ansys/dpf/post/simulation.py b/src/ansys/dpf/post/simulation.py index 8dd54a66f..8ad24c91b 100644 --- a/src/ansys/dpf/post/simulation.py +++ b/src/ansys/dpf/post/simulation.py @@ -16,8 +16,8 @@ from ansys.dpf.core.plotter import DpfPlotter import numpy as np -from ansys.dpf import post from ansys.dpf.post import locations +from ansys.dpf.post.common import elemental_properties from ansys.dpf.post.dataframe import DataFrame from ansys.dpf.post.index import ( CompIndex, @@ -348,8 +348,8 @@ def units(self): def split_mesh_by_properties( self, properties: Union[ - List[post.elemental_properties], - Dict[post.elemental_properties, Union[int, List[int]]], + List[elemental_properties], + Dict[elemental_properties, Union[int, List[int]]], ], ) -> Meshes: """Splits the simulation Mesh according to properties and returns it as Meshes. @@ -368,19 +368,20 @@ def split_mesh_by_properties( Examples -------- >>> from ansys.dpf import post + >>> from ansys.dpf.post.common import elemental_properties >>> from ansys.dpf.post import examples >>> example_path = examples.download_all_kinds_of_complexity() >>> simulation = post.StaticMechanicalSimulation(example_path) >>> # Split by elemental properties and get all resulting meshes >>> meshes_split = simulation.split_mesh_by_properties( - >>> properties=[post.elemental_properties.material, - >>> post.elemental_properties.element_shape] + >>> properties=[elemental_properties.material, + >>> elemental_properties.element_shape] >>> ) >>> # Split by elemental properties and only get meshes for certain property values >>> # Here: split by material and shape, return only for material 1 and shapes 0 and 1 >>> meshes_filtered = simulation.split_mesh_by_properties( - >>> properties={post.elemental_properties.material: 1, - >>> post.elemental_properties.element_shape: [0, 1]} + >>> properties={elemental_properties.material: 1, + >>> elemental_properties.element_shape: [0, 1]} >>> ) """ split_op = dpf.operators.scoping.split_on_property_type( diff --git a/tests/test_simulation.py b/tests/test_simulation.py index 3a77f9963..088ede887 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -6,7 +6,7 @@ from pytest import fixture from ansys.dpf import post -from ansys.dpf.post.common import AvailableSimulationTypes +from ansys.dpf.post.common import AvailableSimulationTypes, elemental_properties from ansys.dpf.post.index import ref_labels from ansys.dpf.post.meshes import Meshes @@ -79,31 +79,31 @@ def test_simulation_split_mesh_by_properties(allkindofcomplexity): simulation = post.StaticMechanicalSimulation(allkindofcomplexity) meshes = simulation.split_mesh_by_properties( properties=[ - post.elemental_properties.material, - post.elemental_properties.element_shape, + elemental_properties.material, + elemental_properties.element_shape, ] ) assert isinstance(meshes, Meshes) assert len(meshes) == 16 meshes = simulation.split_mesh_by_properties( properties={ - post.elemental_properties.material: 1, - post.elemental_properties.element_shape: [0, 1], + elemental_properties.material: 1, + elemental_properties.element_shape: [0, 1], } ) assert isinstance(meshes, Meshes) assert len(meshes) == 2 meshes = simulation.split_mesh_by_properties( properties={ - post.elemental_properties.material: 1, - post.elemental_properties.element_shape: [0, 2], + elemental_properties.material: 1, + elemental_properties.element_shape: [0, 2], } ) assert isinstance(meshes, post.Mesh) meshes = simulation.split_mesh_by_properties( properties={ - post.elemental_properties.material: 22, - post.elemental_properties.element_shape: [0, 2], + elemental_properties.material: 22, + elemental_properties.element_shape: [0, 2], } ) assert meshes is None From 8eb03abb2da3c4a06ba14a8a83dff3b352e8152d Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 28 Mar 2023 12:02:57 +0200 Subject: [PATCH 28/99] Fix Simulation.split_mesh_by_properties docstring --- src/ansys/dpf/post/simulation.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ansys/dpf/post/simulation.py b/src/ansys/dpf/post/simulation.py index 8ad24c91b..f5930a4d4 100644 --- a/src/ansys/dpf/post/simulation.py +++ b/src/ansys/dpf/post/simulation.py @@ -374,15 +374,15 @@ def split_mesh_by_properties( >>> simulation = post.StaticMechanicalSimulation(example_path) >>> # Split by elemental properties and get all resulting meshes >>> meshes_split = simulation.split_mesh_by_properties( - >>> properties=[elemental_properties.material, - >>> elemental_properties.element_shape] - >>> ) + ... properties=[elemental_properties.material, + ... elemental_properties.element_shape] + ... ) >>> # Split by elemental properties and only get meshes for certain property values >>> # Here: split by material and shape, return only for material 1 and shapes 0 and 1 >>> meshes_filtered = simulation.split_mesh_by_properties( - >>> properties={elemental_properties.material: 1, - >>> elemental_properties.element_shape: [0, 1]} - >>> ) + ... properties={elemental_properties.material: 1, + ... elemental_properties.element_shape: [0, 1]} + ... ) """ split_op = dpf.operators.scoping.split_on_property_type( mesh=self._model.metadata.mesh_provider.outputs.mesh, From 9ecfcc55840a786b6cf8a8374d0b93d44281d5d5 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 28 Mar 2023 12:03:41 +0200 Subject: [PATCH 29/99] Add Meshes.plot docstring example --- src/ansys/dpf/post/meshes.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/ansys/dpf/post/meshes.py b/src/ansys/dpf/post/meshes.py index 19cb8c4cc..5580b3546 100644 --- a/src/ansys/dpf/post/meshes.py +++ b/src/ansys/dpf/post/meshes.py @@ -105,7 +105,30 @@ def select(self, **kwargs) -> Union[Mesh, Meshes, None]: return Meshes(meshes_container=meshes_container) def plot(self, **kwargs): - """Plots the Meshes.""" + """Plots all the Mesh objects in the Meshes. + + Parameters + ---------- + kwargs: + Additional keyword arguments for the plotter. For additional keyword + arguments, see ``help(pyvista.plot)``. + + Returns + ------- + A Plotter instance of the current plotting back-end. + + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post import examples + >>> from ansys.dpf.post.common import elemental_properties + >>> example_path = examples.download_all_kinds_of_complexity() + >>> simulation = post.StaticMechanicalSimulation(example_path) + >>> meshes = simulation.split_mesh_by_properties( + ... properties=[elemental_properties.material, elemental_properties.element_shape] + ... ) + >>> meshes.plot() + """ from ansys.dpf.core.plotter import DpfPlotter plt = DpfPlotter(**kwargs) From e55da83d3fd930a6a86e2049db6ebbf801952b54 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 28 Mar 2023 12:34:18 +0200 Subject: [PATCH 30/99] Add Meshes docstring examples --- src/ansys/dpf/post/meshes.py | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/ansys/dpf/post/meshes.py b/src/ansys/dpf/post/meshes.py index 5580b3546..e99fb586f 100644 --- a/src/ansys/dpf/post/meshes.py +++ b/src/ansys/dpf/post/meshes.py @@ -16,7 +16,25 @@ class Meshes: - """Container to hold and interact with a split mesh.""" + """Container to hold and interact with a split mesh. + + Examples + -------- + Selection of a mesh by index or by property values + >>> from ansys.dpf import post + >>> from ansys.dpf.post.common import elemental_properties as elt_prop + >>> from ansys.dpf.post import examples + >>> example_path = examples.download_all_kinds_of_complexity() + >>> simulation = post.StaticMechanicalSimulation(example_path) + >>> # Split by elemental properties and get all resulting meshes + >>> meshes_split = simulation.split_mesh_by_properties( + ... properties=[elt_prop.material, + ... elt_prop.element_shape] + >>> # Select the second mesh by its index (0-based) + >>> mesh_1 = meshes_split[1] + >>> # Select the mesh for material=1 and elshape=0 + >>> mesh_2 = meshes_split[{elt_prop.material: 1, elt_prop.element_shape: 0}] + """ def __init__(self, meshes_container: MeshesContainer): """Initialize this class.""" @@ -41,7 +59,7 @@ def __len__(self): return len(self._core_object) def select(self, **kwargs) -> Union[Mesh, Meshes, None]: - """Select a specific mesh based on a combination of property values. + """Select meshes based on a combination of property values. Parameters ---------- @@ -55,6 +73,18 @@ def select(self, **kwargs) -> Union[Mesh, Meshes, None]: or a Meshes when several are selected, or None when the criteria result in no mesh. + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post.common import elemental_properties + >>> from ansys.dpf.post import examples + >>> example_path = examples.download_all_kinds_of_complexity() + >>> simulation = post.StaticMechanicalSimulation(example_path) + >>> # Split by elemental properties and get all resulting meshes + >>> meshes_split = simulation.split_mesh_by_properties( + ... properties=[elemental_properties.material, + ... elemental_properties.element_shape] + >>> mesh = meshes_split.select(mat=1, elshape=[0, 1]) """ initial_labels = self._core_object.labels # Filter labels From 3f2676385b6ab59a47318ab6dc8ec9faaab13622 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 28 Mar 2023 12:41:24 +0200 Subject: [PATCH 31/99] Update test_meshes.py --- tests/test_meshes.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_meshes.py b/tests/test_meshes.py index a4fe948a6..47275b4ee 100644 --- a/tests/test_meshes.py +++ b/tests/test_meshes.py @@ -3,19 +3,23 @@ from pytest import fixture from ansys.dpf import post +from ansys.dpf.post.common import elemental_properties as elt_prop from ansys.dpf.post.static_mechanical_simulation import StaticMechanicalSimulation @fixture def meshes(allkindofcomplexity): simulation = StaticMechanicalSimulation(allkindofcomplexity) - return simulation.split_mesh_by_properties(properties=["mat", "elshape"]) + return simulation.split_mesh_by_properties( + properties=[elt_prop.material, elt_prop.element_shape] + ) def test_meshes_core_object(meshes): assert isinstance(meshes._core_object, dpf.MeshesContainer) assert ( - "elshape" in meshes._core_object.labels and "mat" in meshes._core_object.labels + elt_prop.element_shape in meshes._core_object.labels + and elt_prop.material in meshes._core_object.labels ) @@ -31,7 +35,7 @@ def test_meshes_get_item(meshes): mesh1 = meshes[1] assert isinstance(mesh1, post.Mesh) assert len(mesh1.node_ids) == 240 - mesh2 = meshes[{"mat": 1, "elshape": 0}] + mesh2 = meshes[{elt_prop.material: 1, elt_prop.element_shape: 0}] assert isinstance(mesh2, post.Mesh) assert len(mesh2.node_ids) == 240 From 2415afe14c8131e99f9e9eda5d09bee42456dcfb Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 28 Mar 2023 19:09:07 +0200 Subject: [PATCH 32/99] Add Mesh.coordinates and Mesh.plot docstring example --- src/ansys/dpf/post/mesh.py | 82 ++++++++++++++++++++++++++++++-- src/ansys/dpf/post/selection.py | 3 +- src/ansys/dpf/post/simulation.py | 2 +- tests/test_mesh.py | 19 +++++++- 4 files changed, 99 insertions(+), 7 deletions(-) diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index 0aae475ef..6bf3e99ab 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -4,15 +4,20 @@ ---- """ +from __future__ import annotations + from typing import List -from ansys.dpf.core import MeshedRegion +import ansys.dpf.core as dpf + +import ansys.dpf.post as post +from ansys.dpf.post import index, locations class Mesh: """Exposes the complete mesh of the simulation.""" - def __init__(self, meshed_region: MeshedRegion): + def __init__(self, meshed_region: dpf.MeshedRegion): """Initialize this class.""" self._meshed_region = meshed_region @@ -41,5 +46,76 @@ def _core_object(self): return self._meshed_region def plot(self, **kwargs): - """Plots the Mesh.""" + """Plots the Mesh. + + Parameters + ---------- + kwargs: + Additional keyword arguments for the plotter. For additional keyword + arguments, see ``help(pyvista.plot)``. + + Returns + ------- + A Plotter instance of the current plotting back-end. + + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post import examples + >>> from ansys.dpf.post.common import elemental_properties + >>> example_path = examples.download_all_kinds_of_complexity() + >>> simulation = post.StaticMechanicalSimulation(example_path) + >>> mesh = simulation.mesh + >>> mesh.plot() + + """ return self._core_object.plot(**kwargs) + + @property + def coordinates(self) -> post.DataFrame: + """Returns the nodal coordinates of the Mesh as a DataFrame. + + Examples + -------- + >>> from ansys.dpf.post import StaticMechanicalSimulation + >>> from ansys.dpf.post import examples + >>> simulation = StaticMechanicalSimulation(examples.static_rst) + >>> mesh = simulation.mesh + >>> coord = mesh.coordinates + >>> print(coord) # doctest: +NORMALIZE_WHITESPACE + results coord (m) + node_ids components + 1 X 1.5000e-02 + Y 4.5000e-02 + Z 1.5000e-02 + 2 X 1.5000e-02 + Y 4.5000e-02 + Z 0.0000e+00 + ... + + """ + from ansys.dpf.post.simulation import vector_component_names + + label = "coord" + fields_container = dpf.FieldsContainer() + fields_container.add_field( + label_space={}, field=self._core_object.nodes.coordinates_field + ) + return post.DataFrame( + data=fields_container, + index=index.MultiIndex( + indexes=[ + index.MeshIndex( + location=locations.nodal, + scoping=self._core_object.nodes.scoping, + fc=fields_container, + ), + index.CompIndex(values=vector_component_names), + ] + ), + columns=index.MultiIndex( + indexes=[ + index.ResultsIndex(values=[label], units=fields_container[0].unit) + ] + ), + ) diff --git a/src/ansys/dpf/post/selection.py b/src/ansys/dpf/post/selection.py index 5b1bee584..af9e49c1a 100644 --- a/src/ansys/dpf/post/selection.py +++ b/src/ansys/dpf/post/selection.py @@ -10,6 +10,7 @@ if TYPE_CHECKING: from ansys.dpf.post.simulation import Simulation + from ansys.dpf.post.mesh import Mesh from typing import Union @@ -28,8 +29,6 @@ from ansys.dpf.core.server_types import BaseServer from numpy import ndarray -from ansys.dpf.post.mesh import Mesh - class _WfNames: data_sources = "data_sources" diff --git a/src/ansys/dpf/post/simulation.py b/src/ansys/dpf/post/simulation.py index f5930a4d4..269ac5749 100644 --- a/src/ansys/dpf/post/simulation.py +++ b/src/ansys/dpf/post/simulation.py @@ -13,11 +13,11 @@ import ansys.dpf.core as dpf from ansys.dpf.core import DataSources, Model, TimeFreqSupport +from ansys.dpf.core.common import elemental_properties from ansys.dpf.core.plotter import DpfPlotter import numpy as np from ansys.dpf.post import locations -from ansys.dpf.post.common import elemental_properties from ansys.dpf.post.dataframe import DataFrame from ansys.dpf.post.index import ( CompIndex, diff --git a/tests/test_mesh.py b/tests/test_mesh.py index d6925b7c9..abb8f53ba 100644 --- a/tests/test_mesh.py +++ b/tests/test_mesh.py @@ -2,7 +2,7 @@ import numpy as np from pytest import fixture -from ansys.dpf.post.static_mechanical_simulation import StaticMechanicalSimulation +from ansys.dpf.post import StaticMechanicalSimulation @fixture @@ -40,3 +40,20 @@ def test_mesh_str(mesh): txt == "DPF Mesh: \n 81 nodes \n 8 elements \n Unit: m \n With solid (3D) elements" ) + + +def test_mesh_coordinates(mesh): + coord = mesh.coordinates + print(coord) + ref = """ + results coord (m) + node_ids components + 1 X 1.5000e-02 + Y 4.5000e-02 + Z 1.5000e-02 + 2 X 1.5000e-02 + Y 4.5000e-02 + Z 0.0000e+00 + ... +""" # noqa + assert str(coord) == ref From c732e38783a89c3d5848c3e0a57da800af120fd4 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 28 Mar 2023 19:11:44 +0200 Subject: [PATCH 33/99] Update draft example with new requirements for the Mesh API --- .../01-Detailed-Examples/05-mesh-exploration.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/examples/01-Detailed-Examples/05-mesh-exploration.py b/examples/01-Detailed-Examples/05-mesh-exploration.py index d8dbff99f..001b530fb 100644 --- a/examples/01-Detailed-Examples/05-mesh-exploration.py +++ b/examples/01-Detailed-Examples/05-mesh-exploration.py @@ -15,6 +15,7 @@ from ansys.dpf import post from ansys.dpf.post import examples +from ansys.dpf.post.common import elemental_properties ############################################################################### # Get ``Simulation`` object @@ -38,14 +39,21 @@ # ------------ # Split the global mesh according to mesh properties -meshes = simulation.split_mesh_by_properties(properties=["mat", "elshape"]) +meshes = simulation.split_mesh_by_properties( + properties=[elemental_properties.material, elemental_properties.element_shape] +) meshes.plot() # Split the global mesh and select meshes for specific property values +print(meshes) meshes = simulation.split_mesh_by_properties( - properties=["mat", "elshape"], - values=[1, [0, 1]], + properties={ + elemental_properties.material: 1, + elemental_properties.element_shape: [0, 1], + } ) + +# Mesh meshes.plot() # Select a specific Mesh in the Meshes, by index @@ -143,6 +151,7 @@ # # # Get the type of the element # e_type = mesh._core_object.elements.element_by_id(1).type +# -> link with element_type Enum and element_property map # # Get the shape of the element # e_shape = mesh._core_object.elements.element_by_id(1).shape # # Get the connectivity of the element From a610f615cc52ca8e1916e60a1e40f7f8907baee4 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Wed, 29 Mar 2023 10:11:54 +0200 Subject: [PATCH 34/99] Fix Meshes docstrings --- src/ansys/dpf/post/meshes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ansys/dpf/post/meshes.py b/src/ansys/dpf/post/meshes.py index e99fb586f..ca7dc802f 100644 --- a/src/ansys/dpf/post/meshes.py +++ b/src/ansys/dpf/post/meshes.py @@ -30,6 +30,7 @@ class Meshes: >>> meshes_split = simulation.split_mesh_by_properties( ... properties=[elt_prop.material, ... elt_prop.element_shape] + ... ) >>> # Select the second mesh by its index (0-based) >>> mesh_1 = meshes_split[1] >>> # Select the mesh for material=1 and elshape=0 @@ -84,6 +85,7 @@ def select(self, **kwargs) -> Union[Mesh, Meshes, None]: >>> meshes_split = simulation.split_mesh_by_properties( ... properties=[elemental_properties.material, ... elemental_properties.element_shape] + ... ) >>> mesh = meshes_split.select(mat=1, elshape=[0, 1]) """ initial_labels = self._core_object.labels From 23d9989d661722803ff635fe093bb5ac6bab7a4c Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Thu, 30 Mar 2023 17:03:11 +0200 Subject: [PATCH 35/99] Completed mesh basic information querying section --- .../05-mesh-exploration.py | 41 ++++---- src/ansys/dpf/post/mesh.py | 95 ++++++++++++++++++- 2 files changed, 113 insertions(+), 23 deletions(-) diff --git a/examples/01-Detailed-Examples/05-mesh-exploration.py b/examples/01-Detailed-Examples/05-mesh-exploration.py index 001b530fb..f489b5278 100644 --- a/examples/01-Detailed-Examples/05-mesh-exploration.py +++ b/examples/01-Detailed-Examples/05-mesh-exploration.py @@ -92,29 +92,26 @@ # Query basic information about the mesh (available in PyDPF-Core) # -------------------------------------- # -# # Number of nodes -# n_nodes = mesh._core_object.nodes.n_nodes -# -# # Number of elements -# n_elements = mesh._core_object.elements.n_elements -# -# # Number of named selections -# n_ns = len(named_selections) -# -# # Unit (get and set) -# mesh_unit = mesh._core_object.unit -# mesh._core_object.unit = "mm" -# -# # Get the list of nodes/elements IDs of a given named selection -# ns_scoping = mesh._core_object.named_selection(named_selection=named_selections[0]) -# ns_ids = ns_scoping.ids -# -# # Named selection setter -# mesh._core_object.set_named_selection_scoping( -# named_selection_name=named_selections[0], -# scoping=ns_scoping, -# ) +# Number of nodes +n_nodes = mesh.num_nodes + +# Number of elements +n_elements = mesh.num_elements + +# Number of named selections +n_ns = len(named_selections) + +# Unit (get and set) +mesh_unit = mesh.unit +mesh.unit = "mm" + +# Get the list of nodes/elements IDs of a given named selection +ns_ids = mesh.named_selections[named_selections[0]] +# Named selection setter +# No update, only creation +ns_name = "test_ns" +mesh.named_selections[ns_name] = [344, 345, 346] # Plot the mesh plt = mesh.plot() diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index 6bf3e99ab..3f77c79cd 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -6,6 +6,7 @@ """ from __future__ import annotations +from collections.abc import Iterator, MutableMapping from typing import List import ansys.dpf.core as dpf @@ -14,6 +15,74 @@ from ansys.dpf.post import index, locations +class NamedSelectionsIterator(Iterator): + """Iterator implementation for NamedSelectionsDict.""" + + def __init__(self, ns_dict: NamedSelectionsDict): + """Initialize the Named Selection Iterator. see NamedSelectionsDict.""" + self.idx = 0 + self.ns_dict = ns_dict + + def __iter__(self) -> NamedSelectionsIterator: + """Get base iterator.""" + self.idx = 0 + return self + + def __next__(self) -> List[int]: + """Returns next value.""" + if self.idx < len(self.ns_dict): + res = self.ns_dict[self.keys()[self.idx]] + self.idx += 1 + return res + else: + raise StopIteration + + +class NamedSelectionsDict(MutableMapping): + """Proxy class to expose Named Selections interface to post.Mesh.""" + + def __init__(self, meshed_region: dpf.MeshedRegion): + """Initialize Named Selections dictionary from internal Meshed Region.""" + self._meshed_region = meshed_region + + def __getitem__(self, key: str) -> List[int]: + """Implements [] getter access function.""" + if key in self._meshed_region.available_named_selections: + return self._meshed_region.named_selection(key).ids + + raise KeyError(f'named selection "{key}" could not be found') + + def __setitem__(self, key: str, value: List[int]): + """Implements [] setter access function.""" + self._meshed_region.set_named_selection_scoping( + named_selection_name=key, scoping=dpf.Scoping(ids=value) + ) + + def __len__(self): + """Returns the length of the dictionary (number of named selections).""" + return len(self.keys()) + + def keys(self): + """Returns the available named selections.""" + return self._meshed_region.available_named_selections + + def values(self): + """Returns list of the values of all the named selections.""" + return [self._meshed_region.named_selection(key) for key in self.keys()] + + def has_key(self, key) -> bool: + """Returns True the given key is present in available named selections.""" + return key in self.keys() + + def __delitem__(self, __key): + """Not implemented.""" + pass + + def __iter__(self) -> NamedSelectionsIterator: + """Returns an iterator to access this dictionary.""" + return NamedSelectionsIterator(self) + + class Mesh: """Exposes the complete mesh of the simulation.""" @@ -27,19 +96,43 @@ def __str__(self): @property def available_named_selections(self) -> List[str]: - """Returns the available named selections of the mesh.""" + """Returns the list of name of available named selections in the mesh.""" return self._meshed_region.available_named_selections + @property + def named_selections(self) -> NamedSelectionsDict: + """Returns the list of named selections in the mesh.""" + return NamedSelectionsDict(self._meshed_region) + @property def node_ids(self) -> List[int]: """Returns the list of node IDs in the mesh.""" return self._meshed_region.nodes.scoping.ids + @property + def num_nodes(self) -> int: + """Returns the number of nodes in the mesh.""" + return len(self.node_ids) + @property def element_ids(self) -> List[int]: """Returns the list of element IDs in the mesh.""" return self._meshed_region.elements.scoping.ids + @property + def num_elements(self) -> int: + """Returns the number of element in the mesh.""" + return len(self.element_ids) + + @property + def unit(self) -> str: + """Returns the unit of the mesh (same as coordinates of the mesh).""" + return self._meshed_region.unit + + @unit.setter + def unit(self, value: str): + self._meshed_region.unit = value + @property def _core_object(self): """Returns the underlying PyDPF-Core class:`ansys.dpf.core.MeshedRegion` object.""" From 913188c01689835e58690afb24273a0db0a63748 Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Fri, 31 Mar 2023 18:17:00 +0200 Subject: [PATCH 36/99] Added Elements, Nodes accessors, moved NamedSelection --- .../05-mesh-exploration.py | 76 +++++++---- src/ansys/dpf/post/elements.py | 125 ++++++++++++++++++ src/ansys/dpf/post/mesh.py | 92 ++++--------- src/ansys/dpf/post/named_selection.py | 100 ++++++++++++++ src/ansys/dpf/post/nodes.py | 27 ++++ 5 files changed, 324 insertions(+), 96 deletions(-) create mode 100644 src/ansys/dpf/post/elements.py create mode 100644 src/ansys/dpf/post/named_selection.py create mode 100644 src/ansys/dpf/post/nodes.py diff --git a/examples/01-Detailed-Examples/05-mesh-exploration.py b/examples/01-Detailed-Examples/05-mesh-exploration.py index f489b5278..0740eaece 100644 --- a/examples/01-Detailed-Examples/05-mesh-exploration.py +++ b/examples/01-Detailed-Examples/05-mesh-exploration.py @@ -106,31 +106,43 @@ mesh.unit = "mm" # Get the list of nodes/elements IDs of a given named selection -ns_ids = mesh.named_selections[named_selections[0]] +first_name = named_selections[0] +named_selection = mesh.named_selections[first_name] +print(named_selection) +for k, v in mesh.named_selections.items(): + print(k) + print(v) # Named selection setter -# No update, only creation -ns_name = "test_ns" -mesh.named_selections[ns_name] = [344, 345, 346] +assert all( + mesh._core_object.named_selection(first_name) + == mesh.named_selections[first_name].ids +) +mesh.named_selections[first_name].ids = [1, 2, 3] +assert all(mesh._core_object.named_selection(first_name).ids == [1, 2, 3]) + + # Plot the mesh plt = mesh.plot() # # ####################################### # # Manipulating elements -# -# # Get an element by ID -# element_by_id = mesh._core_object.elements.element_by_id(1) -# # Get an element by index -# element_by_index = mesh._core_object.elements.element_by_index(0) -# # Get the ID of an element -# e_id = mesh._core_object.elements.element_by_index(0).id -# -# # Adding elements to the mesh -# -# # Get the element types -# e_types = mesh._core_object.elements.element_types_field -# -# # Get the materials + +# Get an element by ID +el_0 = mesh.elements[1] + +# Get an element by index +el_0 = mesh.ielements[0] +el_id = mesh.ielements[0].id + +# Adding elements to the mesh +# Not mutable for now + +# Get the element types +mesh.elements.types +print(mesh.elements.types[el_id]) + +# Get the materials # e_materials = mesh._core_object.elements.materials_field # # # Get the elemental connectivity @@ -139,28 +151,36 @@ # # ############################################## # # Query information about one particular element # -# # Get the nodes of an element +# Get the nodes of an element +mesh.elements[1].nodes # e_nodes = mesh._core_object.elements.element_by_id(1).nodes -# # Get the node IDs of an element +# Get the node IDs of an element +mesh.elements[1].node_ids # e_node_ids = mesh._core_object.elements.element_by_id(1).node_ids -# # Get the nodes of an element +# Get the nodes of an element +mesh.elements[1].n_nodes # e_n_node = mesh._core_object.elements.element_by_id(1).n_nodes # -# # Get the type of the element +# Get the type of the element +mesh.elements[1].type # e_type = mesh._core_object.elements.element_by_id(1).type # -> link with element_type Enum and element_property map -# # Get the shape of the element +# Get the shape of the element +mesh.elements[1].shape # e_shape = mesh._core_object.elements.element_by_id(1).shape -# # Get the connectivity of the element +# Get the connectivity of the element +mesh.elements[1].connectivity # e_connectivity = mesh._core_object.elements.element_by_id(1).connectivity # # # # ################## # # Manipulating nodes # -# # Get a node by ID +# Get a node by ID +node_by_id = mesh.nodes[1] # node_by_id = mesh._core_object.nodes.node_by_id(1) -# # Get a node by index +# Get a node by index +node_by_index = mesh.inodes[0] # node_by_index = mesh._core_object.nodes.node_by_index(0) # # # Get the coordinates of all nodes @@ -169,7 +189,9 @@ # # ########################################### # # Query information about one particular node # -# # Coordinates +# Coordinates +coords = mesh.nodes[1].coordinates # coor = mesh._core_object.nodes.node_by_id(1).coordinates # # Nodal connectivity +conn = mesh.nodes[1].nodal_connectivity # conn = mesh._core_object.nodes.node_by_id(1).nodal_connectivity diff --git a/src/ansys/dpf/post/elements.py b/src/ansys/dpf/post/elements.py new file mode 100644 index 000000000..dd384cdfb --- /dev/null +++ b/src/ansys/dpf/post/elements.py @@ -0,0 +1,125 @@ +"""This module contains ElementList, ElementType and Element classes.""" + +from __future__ import annotations + +from collections.abc import Sequence +from typing import Dict, List, Union + +import ansys.dpf.core as dpf +import ansys.dpf.core.elements as elements +import ansys.dpf.core.nodes as nodes # noqa: F401 + + +class ElementType(dpf.ElementDescriptor): + """Wrapper type to instantiate an ElementDescriptor from an int.""" + + def __init__(self, arg: Union[dpf.ElementDescriptor, int]): + """Constructs an ElementType from an existing descriptor or enum id.""" + _obj = arg + if isinstance(arg, int): + _obj = dpf.element_types.descriptor(arg) + + if not isinstance(_obj, dpf.ElementDescriptor): + raise TypeError(f"Given argument is not an int nor an ElementDescriptor") + + super().__init__( + _obj.enum_id, + _obj.description, + _obj.name, + _obj.shape, + _obj.n_corner_nodes, + _obj.n_mid_nodes, + _obj.n_nodes, + _obj.is_solid, + _obj.is_shell, + _obj.is_beam, + _obj.is_quadratic, + ) + + +class Element: + """Proxy class wrapping dpf.core.elements.Element.""" + + def __init__(self, elements: elements.Elements, index: int): + """Constructs a Proxy Element object.""" + self._elements = elements + self._index = index + + def _resolve(self): + """Returns the original Element object in the original list.""" + return self._elements[self._index] + + @property + def node_ids(self) -> List[int]: + """See :py:meth:`ansys.dpf.core.elements.Element.node_ids`.""" + return self._resolve().node_ids + + @property + def id(self) -> int: + """See :py:meth:`ansys.dpf.core.elements.Element.id`.""" + return self._resolve().id + + @property + def index(self) -> int: + """See :py:meth:`ansys.dpf.core.elements.Element.index`.""" + return self._resolve().index + + @property + def nodes(self) -> List[nodes.Node]: + """See :py:meth:`ansys.dpf.core.elements.Element.nodes`.""" + return self._resolve().nodes + + @property + def n_nodes(self) -> int: + """See :py:meth:`ansys.dpf.core.elements.Element.n_nodes`.""" + return self._resolve().n_nodes + + @property + def type(self) -> ElementType: + """Gets an element descriptor, See :py:meth:`ansys.dpf.core.elements.Element.id`.""" + return ElementType(self._resolve().type.value) + + @property + def shape(self) -> str: + """See :py:meth:`ansys.dpf.core.elements.Element.shape`.""" + return self._resolve().shape + + @property + def connectivity(self) -> List[int]: + """See :py:meth:`ansys.dpf.core.elements.Element.connectivity`.""" + return self._resolve().connectivity + + +class ElementList(Sequence): + """List of Elements.""" + + def __init__(self, elements: elements.Elements, by_id=True): + """Constructs list from existing dpf.core.elements.Elements list.""" + self._elements = elements + self.by_id = by_id + + def __getitem__(self, key: int) -> Element: + """Delegates to element_by_id() if by_id, otherwise to element_by_index().""" + index = key + if self.by_id: + index = self._elements.element_by_id(key).index + + return Element(self._elements, index) + + def __len__(self) -> int: + """Returns the number of elements in the list.""" + return self._elements.n_elements + + # def __repr__(self) -> str: + # return list(iter(self._meshed_region.elements)).__repr__() + + @property + def types(self) -> Dict[int, ElementType]: + """Returns mapping of element id to corresponding type.""" + # TODO: Dataframe + field: dpf.Field = self._elements.element_types_field + keys = field.scoping.ids + + int_to_ed = lambda i: ElementType(int(i)) + vals = map(int_to_ed, field.data) + return dict(zip(keys, vals)) diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index 3f77c79cd..a9c5a345c 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -6,81 +6,15 @@ """ from __future__ import annotations -from collections.abc import Iterator, MutableMapping from typing import List import ansys.dpf.core as dpf import ansys.dpf.post as post from ansys.dpf.post import index, locations - - -class NamedSelectionsIterator(Iterator): - """Iterator implementation for NamedSelectionsDict.""" - - def __init__(self, ns_dict: NamedSelectionsDict): - """Initialize the Named Selection Iterator. see NamedSelectionsDict.""" - self.idx = 0 - self.ns_dict = ns_dict - - def __iter__(self) -> NamedSelectionsIterator: - """Get base iterator.""" - self.idx = 0 - return self - - def __next__(self) -> List[int]: - """Returns next value.""" - if self.idx < len(self.ns_dict): - res = self.ns_dict[self.keys()[self.idx]] - self.idx += 1 - return res - else: - raise StopIteration - - -class NamedSelectionsDict(MutableMapping): - """Proxy class to expose Named Selections interface to post.Mesh.""" - - def __init__(self, meshed_region: dpf.MeshedRegion): - """Initialize Named Selections dictionary from internal Meshed Region.""" - self._meshed_region = meshed_region - - def __getitem__(self, key: str) -> List[int]: - """Implements [] getter access function.""" - if key in self._meshed_region.available_named_selections: - return self._meshed_region.named_selection(key).ids - - raise KeyError(f'named selection "{key}" could not be found') - - def __setitem__(self, key: str, value: List[int]): - """Implements [] setter access function.""" - self._meshed_region.set_named_selection_scoping( - named_selection_name=key, scoping=dpf.Scoping(ids=value) - ) - - def __len__(self): - """Returns the length of the dictionary (number of named selections).""" - return len(self.keys()) - - def keys(self): - """Returns the available named selections.""" - return self._meshed_region.available_named_selections - - def values(self): - """Returns list of the values of all the named selections.""" - return [self._meshed_region.named_selection(key) for key in self.keys()] - - def has_key(self, key) -> bool: - """Returns True the given key is present in available named selections.""" - return key in self.keys() - - def __delitem__(self, __key): - """Not implemented.""" - pass - - def __iter__(self) -> NamedSelectionsIterator: - """Returns an iterator to access this dictionary.""" - return NamedSelectionsIterator(self) +from ansys.dpf.post.elements import ElementList +from ansys.dpf.post.named_selection import NamedSelectionsDict +from ansys.dpf.post.nodes import NodeList class Mesh: @@ -124,6 +58,26 @@ def num_elements(self) -> int: """Returns the number of element in the mesh.""" return len(self.element_ids) + @property + def elements(self) -> ElementList: + """Returns a list of elements indexed by ID.""" + return ElementList(self._meshed_region.elements, by_id=True) + + @property + def ielements(self) -> ElementList: + """Returns a list of element indexed by index (of the original list).""" + return ElementList(self._meshed_region.elements, by_id=False) + + @property + def nodes(self) -> NodeList: + """Returns a list of nodes indexed by ID.""" + return NodeList(self._meshed_region.nodes, by_id=True) + + @property + def inodes(self) -> NodeList: + """Returns a list of nodes indexed by index (of the original list).""" + return NodeList(self._meshed_region.nodes, by_id=False) + @property def unit(self) -> str: """Returns the unit of the mesh (same as coordinates of the mesh).""" diff --git a/src/ansys/dpf/post/named_selection.py b/src/ansys/dpf/post/named_selection.py new file mode 100644 index 000000000..db33d027c --- /dev/null +++ b/src/ansys/dpf/post/named_selection.py @@ -0,0 +1,100 @@ +"""This module contains NamedSelectionsDict, NamedSelectionsIterator and NamedSelection classes.""" + +from __future__ import annotations + +from collections.abc import Iterator, Mapping +from typing import List + +import ansys.dpf.core as dpf + + +class NamedSelectionsIterator(Iterator): + """Iterator implementation for NamedSelectionsDict.""" + + def __init__(self, ns_dict: NamedSelectionsDict): + """Initialize the Named Selection Iterator. see NamedSelectionsDict.""" + self.idx = 0 + self.ns_dict = ns_dict + + def __iter__(self) -> NamedSelectionsIterator: + """Get base iterator.""" + self.idx = 0 + return self + + def __next__(self) -> str: + """Returns next value.""" + if self.idx < len(self.ns_dict): + res = self.ns_dict.keys()[self.idx] + self.idx += 1 + return res + else: + raise StopIteration + + +class NamedSelectionsDict(Mapping): + """Proxy class to expose Named Selections interface to post.Mesh.""" + + def __init__(self, meshed_region: dpf.MeshedRegion): + """Initialize Named Selections dictionary from internal Meshed Region.""" + self._meshed_region = meshed_region + + def __getitem__(self, key: str) -> NamedSelection: + """Implements [] getter access function.""" + if key in self._meshed_region.available_named_selections: + scoping = self._meshed_region.named_selection(key) + return NamedSelection(key, scoping) + + raise KeyError(f'named selection "{key}" could not be found') + + # def __setitem__(self, key: str, value: List[int]): + # """Implements [] setter access function.""" + # self._meshed_region.set_named_selection_scoping( + # named_selection_name=key, scoping=dpf.Scoping(ids=value) + # ) + + def __len__(self) -> int: + """Returns the length of the dictionary (number of named selections).""" + return len(self.keys()) + + def keys(self) -> List[str]: + """Returns the available named selections.""" + return self._meshed_region.available_named_selections + + def values(self) -> List[NamedSelection]: + """Returns list of the values of all the named selections.""" + return [self[key] for key in self.keys()] + + def has_key(self, key) -> bool: + """Returns True the given key is present in available named selections.""" + return key in self.keys() + + def __delitem__(self, __key): + """Not implemented.""" + pass + + def __iter__(self) -> NamedSelectionsIterator: + """Returns an iterator to access this dictionary.""" + return NamedSelectionsIterator(self) + + +class NamedSelection(dpf.Scoping): + """Class decorating dpf.Scoping with a name attribute.""" + + def __init__(self, name: str, scoping: dpf.Scoping): + """Constructs a NamedSelection from a name and a Scoping.""" + super().__init__(scoping) + + self._name = name + + @property + def name(self) -> str: + """Returns the name.""" + return self._name + + # @name.setter + # def name(self, val: str): + # self._name = val + + def __repr__(self) -> str: + """Pretty print string of the NamedSelection.""" + return f"NamedSelection '{self.name}' with scoping {repr(super())}" diff --git a/src/ansys/dpf/post/nodes.py b/src/ansys/dpf/post/nodes.py new file mode 100644 index 000000000..ca93cc2b5 --- /dev/null +++ b/src/ansys/dpf/post/nodes.py @@ -0,0 +1,27 @@ +"""This module contains NodeList class.""" + +from __future__ import annotations + +from collections.abc import Sequence + +import ansys.dpf.core.nodes as nodes + + +class NodeList(Sequence): + """List of Node.""" + + def __init__(self, nodes: nodes.Nodes, by_id=True): + """Constructs a NodeList from an existing dpf.core.nodes.Nodes object.""" + self._nodes = nodes + self.by_id = by_id + + def __getitem__(self, key: int) -> nodes.Node: + """Delegates to node_by_id() if by_id, otherwise to node_by_index().""" + if self.by_id: + return self._nodes.node_by_id(key) + else: + return self._nodes.node_by_index(key) + + def __len__(self) -> int: + """Returns the number of nodes in the list.""" + return self._nodes.n_nodes From 1fd41aa7f880e7c6d96dfdca2e9c2a3c8b9dadb1 Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Thu, 6 Apr 2023 13:45:02 +0200 Subject: [PATCH 37/99] NamedSelection: composition over inheritance --- src/ansys/dpf/post/named_selection.py | 66 +++++++++++++++++---------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/src/ansys/dpf/post/named_selection.py b/src/ansys/dpf/post/named_selection.py index db33d027c..cbc623b19 100644 --- a/src/ansys/dpf/post/named_selection.py +++ b/src/ansys/dpf/post/named_selection.py @@ -3,9 +3,11 @@ from __future__ import annotations from collections.abc import Iterator, Mapping -from typing import List +from typing import List, Union +import copy import ansys.dpf.core as dpf +import ansys.dpf.core.dpf_array as dpf_array class NamedSelectionsIterator(Iterator): @@ -46,27 +48,14 @@ def __getitem__(self, key: str) -> NamedSelection: raise KeyError(f'named selection "{key}" could not be found') - # def __setitem__(self, key: str, value: List[int]): - # """Implements [] setter access function.""" - # self._meshed_region.set_named_selection_scoping( - # named_selection_name=key, scoping=dpf.Scoping(ids=value) - # ) - - def __len__(self) -> int: - """Returns the length of the dictionary (number of named selections).""" - return len(self.keys()) - def keys(self) -> List[str]: """Returns the available named selections.""" return self._meshed_region.available_named_selections - def values(self) -> List[NamedSelection]: - """Returns list of the values of all the named selections.""" - return [self[key] for key in self.keys()] - - def has_key(self, key) -> bool: - """Returns True the given key is present in available named selections.""" - return key in self.keys() + + def __len__(self) -> int: + """Returns the length of the dictionary (number of named selections).""" + return len(self.keys()) def __delitem__(self, __key): """Not implemented.""" @@ -77,13 +66,12 @@ def __iter__(self) -> NamedSelectionsIterator: return NamedSelectionsIterator(self) -class NamedSelection(dpf.Scoping): +class NamedSelection: """Class decorating dpf.Scoping with a name attribute.""" def __init__(self, name: str, scoping: dpf.Scoping): """Constructs a NamedSelection from a name and a Scoping.""" - super().__init__(scoping) - + self._scoping = scoping self._name = name @property @@ -91,10 +79,38 @@ def name(self) -> str: """Returns the name.""" return self._name - # @name.setter - # def name(self, val: str): - # self._name = val + # Scoping forwarding + def set_id(self, index: int, scopingid: int): + self._scoping.set_id(index, scopingid) + + def id(self, index: int) -> int: + return self._scoping.id(index) + + def index(self, id: int) -> int: + return self._scoping.index(id) + @property + def ids(self) -> Union[dpf_array.DPFArray, List[int]]: + return self._scoping.ids + + @property + def location(self) -> str: + return self._scoping.location + + @property + def size(self) -> int: + return self._scoping.size + + def deep_copy(self, server=None) -> NamedSelection: + new_scoping = self._scoping.deep_copy(server) + new_name = copy.copy(self._name) + return NamedSelection(new_name, new_scoping) + + def as_local_scoping(self) -> NamedSelection: + local_scoping = self._scoping.as_local_scoping() + local_name = copy.copy(self._name) + return NamedSelection(local_name, local_scoping) + def __repr__(self) -> str: """Pretty print string of the NamedSelection.""" - return f"NamedSelection '{self.name}' with scoping {repr(super())}" + return f"NamedSelection '{self.name}' with scoping {self._scoping.__str__()}" From 217777e1413f0c20980b476206480f1807968aa9 Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Thu, 6 Apr 2023 14:24:49 +0200 Subject: [PATCH 38/99] Added mock PropertyFieldsContainer for DataFrame, updated Elements --- src/ansys/dpf/post/dataframe.py | 5 +- src/ansys/dpf/post/elements.py | 64 +++++-- src/ansys/dpf/post/fields_container.py | 243 +++++++++++++++++++++++++ src/ansys/dpf/post/mesh.py | 7 +- 4 files changed, 296 insertions(+), 23 deletions(-) create mode 100644 src/ansys/dpf/post/fields_container.py diff --git a/src/ansys/dpf/post/dataframe.py b/src/ansys/dpf/post/dataframe.py index 085884cf2..41786e3c9 100644 --- a/src/ansys/dpf/post/dataframe.py +++ b/src/ansys/dpf/post/dataframe.py @@ -27,6 +27,7 @@ SetIndex, ref_labels, ) +from ansys.dpf.post.fields_container import PropertyFieldsContainer display_width = 100 display_max_colwidth = 12 @@ -38,7 +39,7 @@ class DataFrame: def __init__( self, - data: dpf.FieldsContainer, + data: Union[dpf.FieldsContainer,PropertyFieldsContainer], index: Union[MultiIndex, Index, List[int]], columns: Union[MultiIndex, Index, List[str], None] = None, ): @@ -54,7 +55,7 @@ def __init__( Column indexing (labels) to use. """ self._index = index - if isinstance(data, dpf.FieldsContainer): + if isinstance(data, dpf.FieldsContainer) or isinstance(data, PropertyFieldsContainer): self._fc = data # if index is None: # raise NotImplementedError("Creation from FieldsContainer without index " diff --git a/src/ansys/dpf/post/elements.py b/src/ansys/dpf/post/elements.py index dd384cdfb..e0fd74749 100644 --- a/src/ansys/dpf/post/elements.py +++ b/src/ansys/dpf/post/elements.py @@ -9,6 +9,9 @@ import ansys.dpf.core.elements as elements import ansys.dpf.core.nodes as nodes # noqa: F401 +import ansys.dpf.post as post +from ansys.dpf.post import index, locations +from ansys.dpf.post.fields_container import PropertyFieldsContainer class ElementType(dpf.ElementDescriptor): """Wrapper type to instantiate an ElementDescriptor from an int.""" @@ -36,6 +39,11 @@ def __init__(self, arg: Union[dpf.ElementDescriptor, int]): _obj.is_quadratic, ) + def __str__(self): + return self.name + + def __repr__(self): + return self.__str__() class Element: """Proxy class wrapping dpf.core.elements.Element.""" @@ -89,37 +97,57 @@ def connectivity(self) -> List[int]: """See :py:meth:`ansys.dpf.core.elements.Element.connectivity`.""" return self._resolve().connectivity - -class ElementList(Sequence): +class ElementListIdx(Sequence): """List of Elements.""" - def __init__(self, elements: elements.Elements, by_id=True): + def __init__(self, elements: elements.Elements): """Constructs list from existing dpf.core.elements.Elements list.""" self._elements = elements - self.by_id = by_id - def __getitem__(self, key: int) -> Element: + def __getitem__(self, idx: int) -> Element: """Delegates to element_by_id() if by_id, otherwise to element_by_index().""" - index = key - if self.by_id: - index = self._elements.element_by_id(key).index - - return Element(self._elements, index) + return Element(self._elements, idx) def __len__(self) -> int: """Returns the number of elements in the list.""" return self._elements.n_elements - # def __repr__(self) -> str: - # return list(iter(self._meshed_region.elements)).__repr__() + @property + def by_id(self) -> ElementListById: + return ElementListById(self._elements) @property - def types(self) -> Dict[int, ElementType]: + def types(self) -> post.DataFrame: """Returns mapping of element id to corresponding type.""" - # TODO: Dataframe field: dpf.Field = self._elements.element_types_field - keys = field.scoping.ids + label = "el_type_id" + fields_container = PropertyFieldsContainer() + fields_container.add_field( + label_space={}, field=field + ) + + return post.DataFrame( + data=fields_container, + index=index.MultiIndex( + indexes=[ + index.MeshIndex( + location=locations.elemental, + scoping=self._elements.scoping, + fc=fields_container + ) + ] + ), + columns=index.MultiIndex( + indexes=[ + index.ResultsIndex(values=[label]) + ] + ) + ) + +class ElementListById(ElementListIdx): + def __init__(self, elements: elements.Elements): + super().__init__(elements) - int_to_ed = lambda i: ElementType(int(i)) - vals = map(int_to_ed, field.data) - return dict(zip(keys, vals)) + def __getitem__(self, id: int) -> Element: + idx = self._elements.scoping.index(id) + return super().__getitem__(idx) diff --git a/src/ansys/dpf/post/fields_container.py b/src/ansys/dpf/post/fields_container.py new file mode 100644 index 000000000..9b9824a4c --- /dev/null +++ b/src/ansys/dpf/post/fields_container.py @@ -0,0 +1,243 @@ +from __future__ import annotations + +from ansys import dpf +import ansys.dpf.core as dpf + +from ansys.dpf.core.property_field import PropertyField + +from typing import Union, Dict + +from collections.abc import Sequence +import copy + +class LabelSpaceKV: + def __init__(self, _dict: Dict[str, int], _field): + self._dict = _dict + self._field = _field + + def dict(self): + return self._dict + + def field(self): + return self._field + + def __str__(self): + field_str = str(self._field).replace('\n', ' ') + return f"Label Space: {self._dict} with field {field_str}" + +class PropertyFieldsContainer(Sequence): + def __init__(self, fields_container=None, server=None): + # default constructor + self._labels = [] # used by Dataframe + self.scopings = [] + self.server = None # used by Dataframe + + self.label_spaces = [] + self.ids = [] + + # fields_container copy + if fields_container is not None: + self._labels = copy.deepcopy(fields_container.labels) + self.scopings = copy.deepcopy(fields_container.scopings) + self.server = copy.deepcopy(fields_container.server) + + self.label_spaces = copy.deepcopy(fields_container.label_spaces) + self.ids = copy.deepcopy(fields_container.ids) + # server copy + if server is not None: + self.server = server + + # Collection + def __str__(self): + str = f"DPF PropertyFieldsContainer with {len(self)} fields\n" + for idx, ls in enumerate(self.label_spaces): + str += f"\t {idx}: {ls}\n" + + return str + + @property + def labels(self): + return self._labels + + @labels.setter + def labels(self, vals): + self.set_labels(vals) + + def set_labels(self, labels): + if len(self._labels) != 0: + raise ValueError("labels already set") + + for l in labels: + self.add_label(l) + + def add_label(self, label): + if label not in self._labels: + self._labels.append(label) + self.scopings.append([]) + + def has_label(self, label): + return label in self.labels + + # used by Dataframe + def get_label_space(self, idx): + return self.label_spaces[idx]._dict + + # used by Dataframe + def get_label_scoping(self, label="time"): + if label in self.labels: + scoping_ids = self.scopings[self.labels.index(label)] + return dpf.Scoping(ids=scoping_ids, location="") + raise KeyError("label {label} not found") + + def add_entry(self, label_space: Dict[str, int], value): + new_id = self._new_id() + + if hasattr(value, "_server"): + self.server = value._server + + # add Label Space + self.label_spaces.append(LabelSpaceKV(label_space, value)) + + # Update IDs + self.ids.append(new_id) + + # Update Scopings + for label in label_space.keys(): + label_idx = self.labels.index(label) + self.scopings[label_idx].append(new_id) + + def get_entries(self, label_space_or_index): + if isinstance(label_space_or_index, int): + idx: int = label_space_or_index + return [self.label_spaces[idx].field()] + else: + _dict: Dict[str, int] = label_space_or_index + are_keys_in_labels = [key in self.labels for key in _dict.keys()] + if all(are_keys_in_labels): + remaining = set(range(len(self.label_spaces))) + for key in _dict.keys(): + val = _dict[key] + to_remove = set() + for idx in remaining: + ls = self.label_spaces[idx] + if ls.dict()[key] != val: + to_remove.add(idx) + remaining = remaining.difference(to_remove) + + idx_to_field = lambda idx: self.label_spaces[idx].field() + return list(map(idx_to_field, remaining)) + else: + bad_idx = are_keys_in_labels.index(False) + bad_key = _dict.keys()[bad_idx] + raise KeyError(f"Key {bad_key} is not in labels: {self.labels}") + + def get_entry(self, label_space_or_index): + ret = self.get_entries(label_space_or_index) + + if len(ret) != 0: + return ret[0] + + raise IndexError("Could not find corresponding entry") + + def _new_id(self): + if len(self.ids) == 0: + self.last_id = 1 + return self.last_id + else: + self.last_id += 1 + return self.last_id + + # FieldsContainer + def create_subtype(self, obj_by_copy): + return PropertyField(property_field=obj_by_copy, server=self.server) + + def get_fields_by_time_complex_ids(self, timeid=None, complexid=None): + label_space = {"time": timeid, "complex":complexid} + return self.get_fields(label_space) + + def get_field_by_time_complex_ids(self, timeid=None, complexid=None): + label_space = {"time":timeid, "complex": complexid} + return self.get_field(label_space) + + def __time_complex_label_space__(self, timeid=None, complexid=None): + raise NotImplementedError + + # used by Dataframe + def get_fields(self, label_space): + return self.get_entries(label_space) + + def get_field(self, label_space_or_index): + return self.get_entry(label_space_or_index) + + def get_field_by_time_id(self, timeid=None): + label_space = {"time":timeid} + if self.has_label("complex"): + label_space["complex"] = 1 + return self.get_field(label_space) + + def get_imaginary_fields(self, timeid=None): + label_space = {"time":timeid, "complex":1} + return self.get_fields(label_space) + + def get_imaginary_field(self, timeid=None): + label_space = {"time":timeid, "complex":1} + return self.get_field(label_space) + + # used by Dataframe + def __getitem__(self, key): + return self.get_field(key) + + def __len__(self): + return len(self.label_spaces) + + def add_field(self, label_space, field): + self.add_entry(label_space, field) + + def add_field_by_time_id(self, field, timeid=1): + if not self.has_label("time"): + self.add_label("time") + + label_space = {"time":timeid} + + if self.has_label("complex"): + label_space["complex"] = 0 + + self.add_field(label_space, field) + + def add_imaginary_field(self, field, timeid=1): + if not self.has_label("time"): + self.add_label("time") + if not self.has_label("complex"): + self.add_label("complex") + + label_space = {"time":timeid, "complex":1} + self.add_field(label_space, field) + + def select_component(self, index): + raise NotImplementedError + + @property + def time_freq_support(self): + raise NotImplementedError + + @time_freq_support.setter + def time_freq_support(self, value): + raise NotImplementedError + + def deep_copy(self, server=None): + raise NotImplementedError + + def get_time_scoping(self): + return self.get_label_scoping("time") + + def animate(self, save_as=None, deform_by=None, scale_factor=1.0, **kwargs): + raise NotImplementedError + + def __sub__(self, fields_b): + raise NotImplementedError + + def __pow__(self, value): + raise NotImplementedError + + def __mul__(self, value): + raise NotImplementedError diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index a9c5a345c..b52e12705 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -12,10 +12,11 @@ import ansys.dpf.post as post from ansys.dpf.post import index, locations -from ansys.dpf.post.elements import ElementList +from ansys.dpf.post.elements import ElementListIdx from ansys.dpf.post.named_selection import NamedSelectionsDict from ansys.dpf.post.nodes import NodeList +from ansys.dpf.post.fields_container import PropertyFieldsContainer class Mesh: """Exposes the complete mesh of the simulation.""" @@ -59,9 +60,9 @@ def num_elements(self) -> int: return len(self.element_ids) @property - def elements(self) -> ElementList: + def elements(self) -> ElementListIdx: """Returns a list of elements indexed by ID.""" - return ElementList(self._meshed_region.elements, by_id=True) + return ElementListIdx(self._meshed_region.elements) @property def ielements(self) -> ElementList: From f107ab5125203b0a426d228dfbf29f29d0358c44 Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Thu, 6 Apr 2023 14:43:44 +0200 Subject: [PATCH 39/99] updated Nodes list, added materials property --- src/ansys/dpf/post/mesh.py | 40 +++++++++++++++++++++++++++++++++---- src/ansys/dpf/post/nodes.py | 23 +++++++++++++-------- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index b52e12705..0ccfe999c 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -14,7 +14,7 @@ from ansys.dpf.post import index, locations from ansys.dpf.post.elements import ElementListIdx from ansys.dpf.post.named_selection import NamedSelectionsDict -from ansys.dpf.post.nodes import NodeList +from ansys.dpf.post.nodes import NodeListIdx from ansys.dpf.post.fields_container import PropertyFieldsContainer @@ -65,9 +65,41 @@ def elements(self) -> ElementListIdx: return ElementListIdx(self._meshed_region.elements) @property - def ielements(self) -> ElementList: - """Returns a list of element indexed by index (of the original list).""" - return ElementList(self._meshed_region.elements, by_id=False) + def nodes(self) -> NodeList: + """Returns a list of nodes indexed by ID.""" + return NodeListIdx(self._meshed_region.nodes) + + @property + def element_types(self) -> post.DataFrame: + pass + + @property + def materials(self) -> post.DataFrame: + label = "material_id" + fields_container = PropertyFieldsContainer() + field = self._meshed_region.elements.materials_field + fields_container.add_field( + label_space={}, field=field + ) + + return post.DataFrame( + data=fields_container, + index=index.MultiIndex( + indexes=[ + index.MeshIndex( + location=locations.elemental, + scoping=self._meshed_region.elements.scoping, + fc=fields_container + ) + ] + ), + columns=index.MultiIndex( + indexes=[ + index.ResultsIndex(values=[label]) + ] + ) + ) + @property def nodes(self) -> NodeList: diff --git a/src/ansys/dpf/post/nodes.py b/src/ansys/dpf/post/nodes.py index ca93cc2b5..5013609d9 100644 --- a/src/ansys/dpf/post/nodes.py +++ b/src/ansys/dpf/post/nodes.py @@ -7,21 +7,28 @@ import ansys.dpf.core.nodes as nodes -class NodeList(Sequence): +class NodeListIdx(Sequence): """List of Node.""" - def __init__(self, nodes: nodes.Nodes, by_id=True): + def __init__(self, nodes: nodes.Nodes): """Constructs a NodeList from an existing dpf.core.nodes.Nodes object.""" self._nodes = nodes - self.by_id = by_id - def __getitem__(self, key: int) -> nodes.Node: + def __getitem__(self, idx: int) -> nodes.Node: """Delegates to node_by_id() if by_id, otherwise to node_by_index().""" - if self.by_id: - return self._nodes.node_by_id(key) - else: - return self._nodes.node_by_index(key) + return self._nodes.node_by_index(idx) def __len__(self) -> int: """Returns the number of nodes in the list.""" return self._nodes.n_nodes + + @property + def by_id(self) -> NodeListById: + return NodeListById(self._nodes) + +class NodeListById(NodeListIdx): + def __init__(self, nodes: nodes.Nodes): + super().__init__(nodes) + + def __getitem__(self, id: int) -> nodes.Node: + return self._nodes.node_by_id(id) \ No newline at end of file From 56f1c5d98f7f360c58343e93d627e775c8693b8b Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Thu, 6 Apr 2023 14:58:51 +0200 Subject: [PATCH 40/99] split Element.type into id & info --- src/ansys/dpf/post/elements.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ansys/dpf/post/elements.py b/src/ansys/dpf/post/elements.py index e0fd74749..c96ea2135 100644 --- a/src/ansys/dpf/post/elements.py +++ b/src/ansys/dpf/post/elements.py @@ -83,10 +83,14 @@ def n_nodes(self) -> int: return self._resolve().n_nodes @property - def type(self) -> ElementType: + def type_info(self) -> ElementType: """Gets an element descriptor, See :py:meth:`ansys.dpf.core.elements.Element.id`.""" return ElementType(self._resolve().type.value) + @property + def type_id(self) -> int: + return self._resolve().type.value + @property def shape(self) -> str: """See :py:meth:`ansys.dpf.core.elements.Element.shape`.""" From d8e8414b61c12ff466bce747aa6cad9f9701b610 Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Thu, 13 Apr 2023 18:02:02 +0200 Subject: [PATCH 41/99] Added Simple ConnectivityList --- src/ansys/dpf/post/connectivity.py | 58 ++++++++++++++++++++++++++++++ src/ansys/dpf/post/elements.py | 6 ++++ src/ansys/dpf/post/mesh.py | 29 +++++++++++---- 3 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 src/ansys/dpf/post/connectivity.py diff --git a/src/ansys/dpf/post/connectivity.py b/src/ansys/dpf/post/connectivity.py new file mode 100644 index 000000000..7df173544 --- /dev/null +++ b/src/ansys/dpf/post/connectivity.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +from collections.abc import Sequence, Iterator +from enum import Enum +from ansys.dpf.core.property_field import PropertyField +from ansys.dpf.core.scoping import Scoping + +from typing import List + +class Mode(Enum): + IDS_FROM_IDX = 1 + IDS_FROM_ID = 2 + IDX_FROM_IDX = 3 + IDX_FROM_ID = 4 + +class ConnectivityList(Sequence): + def __init__(self, field: PropertyField, scoping: Scoping, mode: Mode): + self._field = field + self._mode = mode + self._scoping = scoping + + def __getitem__(self, key: int) -> List[int]: + if self._mode == Mode.IDS_FROM_IDX: + return self._get_ids_from_idx(key) + elif self._mode == Mode.IDS_FROM_ID: + return self._get_ids_from_id(key) + elif self._mode == Mode.IDX_FROM_IDX: + return self._get_idx_from_idx(key) + elif self._mode == Mode.IDX_FROM_ID: + return self._get_idx_from_id(key) + + def _get_ids_from_idx(self, idx: int) -> List[int]: + return self._to_ids(self._field.get_entity_data(idx)) + + def _get_ids_from_id(self, id: int) -> List[int]: + return self._to_ids(self._field.get_entity_data_by_id(id)) + + def _get_idx_from_idx(self, idx: int) -> List[int]: + return self._field.get_entity_data(idx) + + def _get_idx_from_id(self, id: int) -> List[int]: + return self._field.get_entity_data_by_id(id) + + @property + def by_id(self) -> ConnectivityList: + if self._mode == Mode.IDS_FROM_IDX: + return ConnectivityList(self._field, self._scoping, Mode.IDS_FROM_ID) + elif self._mode == Mode.IDX_FROM_IDX: + return ConnectivityList(self._field, self._scoping, Mode.IDX_FROM_ID) + return self + + def _to_ids(self, indices) -> List[int]: + to_id = self._scoping.id + return list(map(to_id, indices)) + + def __len__(self) -> int: + return len(self._field._get_data_pointer()) + diff --git a/src/ansys/dpf/post/elements.py b/src/ansys/dpf/post/elements.py index c96ea2135..2dfe04563 100644 --- a/src/ansys/dpf/post/elements.py +++ b/src/ansys/dpf/post/elements.py @@ -100,6 +100,12 @@ def shape(self) -> str: def connectivity(self) -> List[int]: """See :py:meth:`ansys.dpf.core.elements.Element.connectivity`.""" return self._resolve().connectivity + + def __repr__(self) -> str: + return self._resolve().__repr__() + + def __str__(self) -> str: + return self._resolve().__str__() class ElementListIdx(Sequence): """List of Elements.""" diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index 0ccfe999c..6e56391cb 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -15,6 +15,7 @@ from ansys.dpf.post.elements import ElementListIdx from ansys.dpf.post.named_selection import NamedSelectionsDict from ansys.dpf.post.nodes import NodeListIdx +from ansys.dpf.post.connectivity import ConnectivityList, Mode from ansys.dpf.post.fields_container import PropertyFieldsContainer @@ -65,7 +66,7 @@ def elements(self) -> ElementListIdx: return ElementListIdx(self._meshed_region.elements) @property - def nodes(self) -> NodeList: + def nodes(self) -> NodeListIdx: """Returns a list of nodes indexed by ID.""" return NodeListIdx(self._meshed_region.nodes) @@ -100,16 +101,30 @@ def materials(self) -> post.DataFrame: ) ) + @property + def conn_elem_to_node_id(self): + conn_field = self._meshed_region.elements.connectivities_field + nodes_scoping = self._meshed_region.nodes.scoping + return ConnectivityList(conn_field, nodes_scoping, Mode.IDS_FROM_IDX) @property - def nodes(self) -> NodeList: - """Returns a list of nodes indexed by ID.""" - return NodeList(self._meshed_region.nodes, by_id=True) + def conn_node_to_elem_id(self): + conn_field = self._meshed_region.nodes.nodal_connectivity_field + elems_scoping = self._meshed_region.elements.scoping + return ConnectivityList(conn_field, elems_scoping, Mode.IDS_FROM_IDX) @property - def inodes(self) -> NodeList: - """Returns a list of nodes indexed by index (of the original list).""" - return NodeList(self._meshed_region.nodes, by_id=False) + def conn_elem_to_node(self): + conn_field = self._meshed_region.elements.connectivities_field + nodes_scoping = self._meshed_region.nodes.scoping + return ConnectivityList(conn_field, nodes_scoping, Mode.IDX_FROM_IDX) + + @property + def conn_node_to_elem(self): + conn_field = self._meshed_region.nodes.nodal_connectivity_field + elems_scoping = self._meshed_region.elements.scoping + return ConnectivityList(conn_field, elems_scoping, Mode.IDX_FROM_IDX) + @property def unit(self) -> str: From 7b64975d9d8192d948fbe0233504bfd3d16c6a3f Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Thu, 13 Apr 2023 18:05:59 +0200 Subject: [PATCH 42/99] Added element types getter --- src/ansys/dpf/post/mesh.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index 6e56391cb..9fade5665 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -72,8 +72,31 @@ def nodes(self) -> NodeListIdx: @property def element_types(self) -> post.DataFrame: - pass + label = "elem_type_id" + fields_container = PropertyFieldsContainer() + field = self._meshed_region.elements.element_types_field + fields_container.add_field( + label_space={}, field=field + ) + return post.DataFrame( + data=fields_container, + index=index.MultiIndex( + indexes=[ + index.MeshIndex( + location=locations.elemental, + scoping=self._meshed_region.elements.scoping, + fc=fields_container + ) + ] + ), + columns=index.MultiIndex( + indexes=[ + index.ResultsIndex(values=[label]) + ] + ) + ) + @property def materials(self) -> post.DataFrame: label = "material_id" From ccec46b2603bf10e7027aada726d94803ebd7179 Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Mon, 17 Apr 2023 11:43:20 +0200 Subject: [PATCH 43/99] Completed doc and pre-commit checks --- src/ansys/dpf/post/connectivity.py | 36 +++++-- src/ansys/dpf/post/dataframe.py | 8 +- src/ansys/dpf/post/elements.py | 40 +++++--- src/ansys/dpf/post/fields_container.py | 132 +++++++++++++++++-------- src/ansys/dpf/post/mesh.py | 41 ++++---- src/ansys/dpf/post/named_selection.py | 21 ++-- src/ansys/dpf/post/nodes.py | 18 +++- 7 files changed, 191 insertions(+), 105 deletions(-) diff --git a/src/ansys/dpf/post/connectivity.py b/src/ansys/dpf/post/connectivity.py index 7df173544..60ea5f9f1 100644 --- a/src/ansys/dpf/post/connectivity.py +++ b/src/ansys/dpf/post/connectivity.py @@ -1,26 +1,36 @@ +"""Module containing wrapper class for the connectivities property fields.""" + from __future__ import annotations -from collections.abc import Sequence, Iterator +from collections.abc import Sequence from enum import Enum +from typing import List + from ansys.dpf.core.property_field import PropertyField from ansys.dpf.core.scoping import Scoping -from typing import List class Mode(Enum): + """Enum made for internal use, to dictate the behavior of ConnectivityList.""" + IDS_FROM_IDX = 1 - IDS_FROM_ID = 2 + IDS_FROM_ID = 2 IDX_FROM_IDX = 3 - IDX_FROM_ID = 4 + IDX_FROM_ID = 4 + class ConnectivityList(Sequence): + """Very basic wrapper around elemental and nodal connectivities fields.""" + def __init__(self, field: PropertyField, scoping: Scoping, mode: Mode): + """Constructs a ConnectivityList by wrapping given PropertyField.""" self._field = field self._mode = mode self._scoping = scoping def __getitem__(self, key: int) -> List[int]: - if self._mode == Mode.IDS_FROM_IDX: + """Returns a list of indexes or IDs for a given index or ID, see Mode Enum.""" + if self._mode == Mode.IDS_FROM_IDX: return self._get_ids_from_idx(key) elif self._mode == Mode.IDS_FROM_ID: return self._get_ids_from_id(key) @@ -28,21 +38,26 @@ def __getitem__(self, key: int) -> List[int]: return self._get_idx_from_idx(key) elif self._mode == Mode.IDX_FROM_ID: return self._get_idx_from_id(key) - + def _get_ids_from_idx(self, idx: int) -> List[int]: + """Helper method to retrieve list of IDs from a given index.""" return self._to_ids(self._field.get_entity_data(idx)) - + def _get_ids_from_id(self, id: int) -> List[int]: + """Helper method to retrieve list of IDs from a given ID.""" return self._to_ids(self._field.get_entity_data_by_id(id)) - + def _get_idx_from_idx(self, idx: int) -> List[int]: + """Helper method to retrieve list of indexes from a given index.""" return self._field.get_entity_data(idx) def _get_idx_from_id(self, id: int) -> List[int]: + """Helper method to retrieve list of indexes from a given ID.""" return self._field.get_entity_data_by_id(id) - + @property def by_id(self) -> ConnectivityList: + """Returns an equivalent list which accepts an ID instead of an index in __getitem__.""" if self._mode == Mode.IDS_FROM_IDX: return ConnectivityList(self._field, self._scoping, Mode.IDS_FROM_ID) elif self._mode == Mode.IDX_FROM_IDX: @@ -50,9 +65,10 @@ def by_id(self) -> ConnectivityList: return self def _to_ids(self, indices) -> List[int]: + """Helper method to convert a list of indexes into a list of IDs.""" to_id = self._scoping.id return list(map(to_id, indices)) def __len__(self) -> int: + """Returns the number of entities.""" return len(self._field._get_data_pointer()) - diff --git a/src/ansys/dpf/post/dataframe.py b/src/ansys/dpf/post/dataframe.py index 41786e3c9..907190a84 100644 --- a/src/ansys/dpf/post/dataframe.py +++ b/src/ansys/dpf/post/dataframe.py @@ -17,6 +17,7 @@ import numpy as np from ansys.dpf.post import locations, shell_layers +from ansys.dpf.post.fields_container import PropertyFieldsContainer from ansys.dpf.post.index import ( CompIndex, Index, @@ -27,7 +28,6 @@ SetIndex, ref_labels, ) -from ansys.dpf.post.fields_container import PropertyFieldsContainer display_width = 100 display_max_colwidth = 12 @@ -39,7 +39,7 @@ class DataFrame: def __init__( self, - data: Union[dpf.FieldsContainer,PropertyFieldsContainer], + data: Union[dpf.FieldsContainer, PropertyFieldsContainer], index: Union[MultiIndex, Index, List[int]], columns: Union[MultiIndex, Index, List[str], None] = None, ): @@ -55,7 +55,9 @@ def __init__( Column indexing (labels) to use. """ self._index = index - if isinstance(data, dpf.FieldsContainer) or isinstance(data, PropertyFieldsContainer): + if isinstance(data, dpf.FieldsContainer) or isinstance( + data, PropertyFieldsContainer + ): self._fc = data # if index is None: # raise NotImplementedError("Creation from FieldsContainer without index " diff --git a/src/ansys/dpf/post/elements.py b/src/ansys/dpf/post/elements.py index 2dfe04563..af7fce427 100644 --- a/src/ansys/dpf/post/elements.py +++ b/src/ansys/dpf/post/elements.py @@ -3,7 +3,7 @@ from __future__ import annotations from collections.abc import Sequence -from typing import Dict, List, Union +from typing import List, Union import ansys.dpf.core as dpf import ansys.dpf.core.elements as elements @@ -11,7 +11,8 @@ import ansys.dpf.post as post from ansys.dpf.post import index, locations -from ansys.dpf.post.fields_container import PropertyFieldsContainer +from ansys.dpf.post.fields_container import PropertyFieldsContainer + class ElementType(dpf.ElementDescriptor): """Wrapper type to instantiate an ElementDescriptor from an int.""" @@ -40,11 +41,14 @@ def __init__(self, arg: Union[dpf.ElementDescriptor, int]): ) def __str__(self): + """Returns a string representation of the Element Type.""" return self.name - + def __repr__(self): + """Returns a string representation of the Element Type.""" return self.__str__() + class Element: """Proxy class wrapping dpf.core.elements.Element.""" @@ -89,6 +93,7 @@ def type_info(self) -> ElementType: @property def type_id(self) -> int: + """Returns the ID of the Element Type.""" return self._resolve().type.value @property @@ -100,13 +105,16 @@ def shape(self) -> str: def connectivity(self) -> List[int]: """See :py:meth:`ansys.dpf.core.elements.Element.connectivity`.""" return self._resolve().connectivity - + def __repr__(self) -> str: + """Returns string representation of an Element.""" return self._resolve().__repr__() - + def __str__(self) -> str: + """Returns string representation of an Element.""" return self._resolve().__str__() + class ElementListIdx(Sequence): """List of Elements.""" @@ -124,6 +132,7 @@ def __len__(self) -> int: @property def by_id(self) -> ElementListById: + """Returns an equivalent list accessible with ID instead of index.""" return ElementListById(self._elements) @property @@ -132,9 +141,7 @@ def types(self) -> post.DataFrame: field: dpf.Field = self._elements.element_types_field label = "el_type_id" fields_container = PropertyFieldsContainer() - fields_container.add_field( - label_space={}, field=field - ) + fields_container.add_field(label_space={}, field=field) return post.DataFrame( data=fields_container, @@ -143,21 +150,26 @@ def types(self) -> post.DataFrame: index.MeshIndex( location=locations.elemental, scoping=self._elements.scoping, - fc=fields_container + fc=fields_container, ) ] ), - columns=index.MultiIndex( - indexes=[ - index.ResultsIndex(values=[label]) - ] - ) + columns=index.MultiIndex(indexes=[index.ResultsIndex(values=[label])]), ) + class ElementListById(ElementListIdx): + """Wrapper class for accessing Elements by ID instead of index.""" + def __init__(self, elements: elements.Elements): + """Constructs an ElementListById from an Elements instance.""" super().__init__(elements) def __getitem__(self, id: int) -> Element: + """Access an Element with an ID.""" idx = self._elements.scoping.index(id) return super().__getitem__(idx) + + def __len__(self) -> int: + """Returns the number of elements in the list.""" + return self._elements.n_elements diff --git a/src/ansys/dpf/post/fields_container.py b/src/ansys/dpf/post/fields_container.py index 9b9824a4c..2b242ad9f 100644 --- a/src/ansys/dpf/post/fields_container.py +++ b/src/ansys/dpf/post/fields_container.py @@ -1,100 +1,120 @@ +"""Module containing a minimal implementation of PropertyFieldContainer on pydpf-post side.""" + from __future__ import annotations -from ansys import dpf -import ansys.dpf.core as dpf +from collections.abc import Sequence +import copy +from typing import Dict +import ansys.dpf.core as dpf from ansys.dpf.core.property_field import PropertyField -from typing import Union, Dict +from ansys import dpf -from collections.abc import Sequence -import copy class LabelSpaceKV: + """Class for internal use to associate a label space with a field.""" + def __init__(self, _dict: Dict[str, int], _field): - self._dict = _dict + """Constructs an association between a dictionary and a field.""" + self._dict = _dict self._field = _field def dict(self): + """Returns the associated dictionary.""" return self._dict def field(self): + """Returns the associated field.""" return self._field - + def __str__(self): - field_str = str(self._field).replace('\n', ' ') + """Returns a string representationf of the association.""" + field_str = str(self._field).replace("\n", " ") return f"Label Space: {self._dict} with field {field_str}" + class PropertyFieldsContainer(Sequence): + """Minimal implementation of a FieldsContainer specialized for PropertyFieldsContainer.""" + def __init__(self, fields_container=None, server=None): + """Constructs an empty PropertyFieldsContainer or from a PropertyFieldsContainer.""" # default constructor - self._labels = [] # used by Dataframe - self.scopings = [] - self.server = None # used by Dataframe - - self.label_spaces = [] - self.ids = [] - + self._labels = [] # used by Dataframe + self.scopings = [] + self.server = None # used by Dataframe + + self.label_spaces = [] + self.ids = [] + # fields_container copy if fields_container is not None: - self._labels = copy.deepcopy(fields_container.labels) - self.scopings = copy.deepcopy(fields_container.scopings) - self.server = copy.deepcopy(fields_container.server) - - self.label_spaces = copy.deepcopy(fields_container.label_spaces) - self.ids = copy.deepcopy(fields_container.ids) + self._labels = copy.deepcopy(fields_container.labels) + self.scopings = copy.deepcopy(fields_container.scopings) + self.server = copy.deepcopy(fields_container.server) + + self.label_spaces = copy.deepcopy(fields_container.label_spaces) + self.ids = copy.deepcopy(fields_container.ids) # server copy if server is not None: self.server = server - + # Collection def __str__(self): + """Returns a string representation of a PropertyFieldsContainer.""" str = f"DPF PropertyFieldsContainer with {len(self)} fields\n" for idx, ls in enumerate(self.label_spaces): str += f"\t {idx}: {ls}\n" - + return str @property def labels(self): + """Returns all labels of the PropertyFieldsContainer.""" return self._labels - + @labels.setter def labels(self, vals): self.set_labels(vals) def set_labels(self, labels): + """Sets all the label of the PropertyFieldsContainer.""" if len(self._labels) != 0: raise ValueError("labels already set") for l in labels: self.add_label(l) - + def add_label(self, label): + """Adds a label.""" if label not in self._labels: self._labels.append(label) self.scopings.append([]) - + def has_label(self, label): + """Check if a PorpertyFieldsContainer contains a given label.""" return label in self.labels - + # used by Dataframe def get_label_space(self, idx): + """Get a Label Space at a given index.""" return self.label_spaces[idx]._dict - + # used by Dataframe def get_label_scoping(self, label="time"): + """Returns a scoping on the fields concerned by the given label.""" if label in self.labels: scoping_ids = self.scopings[self.labels.index(label)] return dpf.Scoping(ids=scoping_ids, location="") raise KeyError("label {label} not found") - + def add_entry(self, label_space: Dict[str, int], value): + """Adds a PropertyField associated with a dictionary.""" new_id = self._new_id() - + if hasattr(value, "_server"): self.server = value._server - + # add Label Space self.label_spaces.append(LabelSpaceKV(label_space, value)) @@ -105,8 +125,9 @@ def add_entry(self, label_space: Dict[str, int], value): for label in label_space.keys(): label_idx = self.labels.index(label) self.scopings[label_idx].append(new_id) - + def get_entries(self, label_space_or_index): + """Returns a list of fields from a complete or partial specification of a dictionary.""" if isinstance(label_space_or_index, int): idx: int = label_space_or_index return [self.label_spaces[idx].field()] @@ -123,7 +144,7 @@ def get_entries(self, label_space_or_index): if ls.dict()[key] != val: to_remove.add(idx) remaining = remaining.difference(to_remove) - + idx_to_field = lambda idx: self.label_spaces[idx].field() return list(map(idx_to_field, remaining)) else: @@ -132,6 +153,7 @@ def get_entries(self, label_space_or_index): raise KeyError(f"Key {bad_key} is not in labels: {self.labels}") def get_entry(self, label_space_or_index): + """Returns the field or (first field found) corresponding to the given dictionary.""" ret = self.get_entries(label_space_or_index) if len(ret) != 0: @@ -140,6 +162,7 @@ def get_entry(self, label_space_or_index): raise IndexError("Could not find corresponding entry") def _new_id(self): + """Helper method generating a new id when calling add_entry(...).""" if len(self.ids) == 0: self.last_id = 1 return self.last_id @@ -149,95 +172,118 @@ def _new_id(self): # FieldsContainer def create_subtype(self, obj_by_copy): + """Instantiate a PropertyField with given instance, using the server of the container.""" return PropertyField(property_field=obj_by_copy, server=self.server) def get_fields_by_time_complex_ids(self, timeid=None, complexid=None): - label_space = {"time": timeid, "complex":complexid} + """Returns fields at a requested time or complex ID.""" + label_space = {"time": timeid, "complex": complexid} return self.get_fields(label_space) def get_field_by_time_complex_ids(self, timeid=None, complexid=None): - label_space = {"time":timeid, "complex": complexid} + """Returns field at a requested time or complex ID.""" + label_space = {"time": timeid, "complex": complexid} return self.get_field(label_space) def __time_complex_label_space__(self, timeid=None, complexid=None): + """Not implemented.""" raise NotImplementedError # used by Dataframe def get_fields(self, label_space): + """Returns the list of fields associated with given label space.""" return self.get_entries(label_space) def get_field(self, label_space_or_index): + """Retrieves the field at a requested index or label space.""" return self.get_entry(label_space_or_index) def get_field_by_time_id(self, timeid=None): - label_space = {"time":timeid} + """Retrieves the complex field at a requested timeid.""" + label_space = {"time": timeid} if self.has_label("complex"): - label_space["complex"] = 1 + label_space["complex"] = 0 return self.get_field(label_space) def get_imaginary_fields(self, timeid=None): - label_space = {"time":timeid, "complex":1} + """Retrieve the complex fields at a requested timeid.""" + label_space = {"time": timeid, "complex": 1} return self.get_fields(label_space) def get_imaginary_field(self, timeid=None): - label_space = {"time":timeid, "complex":1} + """Retrieve the complex field at a requested time.""" + label_space = {"time": timeid, "complex": 1} return self.get_field(label_space) # used by Dataframe def __getitem__(self, key): + """Retrieve the field at a requested index.""" return self.get_field(key) def __len__(self): + """Retrieve the number of label spaces.""" return len(self.label_spaces) def add_field(self, label_space, field): + """Add or update a field at a requested label space.""" self.add_entry(label_space, field) def add_field_by_time_id(self, field, timeid=1): + """Add or update a field at a requested timeid.""" if not self.has_label("time"): self.add_label("time") - label_space = {"time":timeid} + label_space = {"time": timeid} if self.has_label("complex"): label_space["complex"] = 0 - + self.add_field(label_space, field) def add_imaginary_field(self, field, timeid=1): + """Add or update an imaginary field at a requested timeid.""" if not self.has_label("time"): self.add_label("time") if not self.has_label("complex"): self.add_label("complex") - - label_space = {"time":timeid, "complex":1} + + label_space = {"time": timeid, "complex": 1} self.add_field(label_space, field) def select_component(self, index): + """Not implemented.""" raise NotImplementedError @property def time_freq_support(self): + """Not implemented.""" raise NotImplementedError @time_freq_support.setter def time_freq_support(self, value): + """Not implemented.""" raise NotImplementedError def deep_copy(self, server=None): + """Not implemented.""" raise NotImplementedError def get_time_scoping(self): + """Retrieves the time scoping containing the time sets.""" return self.get_label_scoping("time") def animate(self, save_as=None, deform_by=None, scale_factor=1.0, **kwargs): + """Not implemented.""" raise NotImplementedError def __sub__(self, fields_b): + """Not implemented.""" raise NotImplementedError def __pow__(self, value): + """Not implemented.""" raise NotImplementedError def __mul__(self, value): + """Not implemented.""" raise NotImplementedError diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index 9fade5665..d4dcc647b 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -12,12 +12,12 @@ import ansys.dpf.post as post from ansys.dpf.post import index, locations +from ansys.dpf.post.connectivity import ConnectivityList, Mode from ansys.dpf.post.elements import ElementListIdx +from ansys.dpf.post.fields_container import PropertyFieldsContainer from ansys.dpf.post.named_selection import NamedSelectionsDict from ansys.dpf.post.nodes import NodeListIdx -from ansys.dpf.post.connectivity import ConnectivityList, Mode -from ansys.dpf.post.fields_container import PropertyFieldsContainer class Mesh: """Exposes the complete mesh of the simulation.""" @@ -69,15 +69,14 @@ def elements(self) -> ElementListIdx: def nodes(self) -> NodeListIdx: """Returns a list of nodes indexed by ID.""" return NodeListIdx(self._meshed_region.nodes) - + @property def element_types(self) -> post.DataFrame: + """Returns a DataFrame containing element types ID.""" label = "elem_type_id" fields_container = PropertyFieldsContainer() field = self._meshed_region.elements.element_types_field - fields_container.add_field( - label_space={}, field=field - ) + fields_container.add_field(label_space={}, field=field) return post.DataFrame( data=fields_container, @@ -86,25 +85,20 @@ def element_types(self) -> post.DataFrame: index.MeshIndex( location=locations.elemental, scoping=self._meshed_region.elements.scoping, - fc=fields_container + fc=fields_container, ) ] ), - columns=index.MultiIndex( - indexes=[ - index.ResultsIndex(values=[label]) - ] - ) + columns=index.MultiIndex(indexes=[index.ResultsIndex(values=[label])]), ) - + @property def materials(self) -> post.DataFrame: + """Returns a DataFrame containing element materials ID.""" label = "material_id" fields_container = PropertyFieldsContainer() field = self._meshed_region.elements.materials_field - fields_container.add_field( - label_space={}, field=field - ) + fields_container.add_field(label_space={}, field=field) return post.DataFrame( data=fields_container, @@ -113,41 +107,40 @@ def materials(self) -> post.DataFrame: index.MeshIndex( location=locations.elemental, scoping=self._meshed_region.elements.scoping, - fc=fields_container + fc=fields_container, ) ] ), - columns=index.MultiIndex( - indexes=[ - index.ResultsIndex(values=[label]) - ] - ) + columns=index.MultiIndex(indexes=[index.ResultsIndex(values=[label])]), ) - + @property def conn_elem_to_node_id(self): + """Returns a list of Node ID for a given Element index.""" conn_field = self._meshed_region.elements.connectivities_field nodes_scoping = self._meshed_region.nodes.scoping return ConnectivityList(conn_field, nodes_scoping, Mode.IDS_FROM_IDX) @property def conn_node_to_elem_id(self): + """Returns a list of Element ID for a given Node index.""" conn_field = self._meshed_region.nodes.nodal_connectivity_field elems_scoping = self._meshed_region.elements.scoping return ConnectivityList(conn_field, elems_scoping, Mode.IDS_FROM_IDX) @property def conn_elem_to_node(self): + """Returns a list of Node index for a given Element index.""" conn_field = self._meshed_region.elements.connectivities_field nodes_scoping = self._meshed_region.nodes.scoping return ConnectivityList(conn_field, nodes_scoping, Mode.IDX_FROM_IDX) @property def conn_node_to_elem(self): + """Returns a list of Element index for a given Node index.""" conn_field = self._meshed_region.nodes.nodal_connectivity_field elems_scoping = self._meshed_region.elements.scoping return ConnectivityList(conn_field, elems_scoping, Mode.IDX_FROM_IDX) - @property def unit(self) -> str: diff --git a/src/ansys/dpf/post/named_selection.py b/src/ansys/dpf/post/named_selection.py index cbc623b19..f3ad89dae 100644 --- a/src/ansys/dpf/post/named_selection.py +++ b/src/ansys/dpf/post/named_selection.py @@ -3,8 +3,8 @@ from __future__ import annotations from collections.abc import Iterator, Mapping -from typing import List, Union import copy +from typing import List, Union import ansys.dpf.core as dpf import ansys.dpf.core.dpf_array as dpf_array @@ -52,7 +52,6 @@ def keys(self) -> List[str]: """Returns the available named selections.""" return self._meshed_region.available_named_selections - def __len__(self) -> int: """Returns the length of the dictionary (number of named selections).""" return len(self.keys()) @@ -81,36 +80,44 @@ def name(self) -> str: # Scoping forwarding def set_id(self, index: int, scopingid: int): + """Sets the ID of the underlying scoping's index.""" self._scoping.set_id(index, scopingid) def id(self, index: int) -> int: + """Retrieve the ID at a given index.""" return self._scoping.id(index) def index(self, id: int) -> int: + """Retrieve the index of a given ID.""" return self._scoping.index(id) @property def ids(self) -> Union[dpf_array.DPFArray, List[int]]: + """Retrieve a list of IDs in the underlying scoping.""" return self._scoping.ids - + @property def location(self) -> str: + """Location of the IDs as a string.""" return self._scoping.location @property def size(self) -> int: + """Length of the list of IDs.""" return self._scoping.size def deep_copy(self, server=None) -> NamedSelection: + """Create a deep copy of the underlying scoping's data on a given server.""" new_scoping = self._scoping.deep_copy(server) - new_name = copy.copy(self._name) + new_name = copy.copy(self._name) return NamedSelection(new_name, new_scoping) - + def as_local_scoping(self) -> NamedSelection: + """Create a deep copy of the underlying scoping that can be modified locally.""" local_scoping = self._scoping.as_local_scoping() - local_name = copy.copy(self._name) + local_name = copy.copy(self._name) return NamedSelection(local_name, local_scoping) - + def __repr__(self) -> str: """Pretty print string of the NamedSelection.""" return f"NamedSelection '{self.name}' with scoping {self._scoping.__str__()}" diff --git a/src/ansys/dpf/post/nodes.py b/src/ansys/dpf/post/nodes.py index 5013609d9..33eb476c4 100644 --- a/src/ansys/dpf/post/nodes.py +++ b/src/ansys/dpf/post/nodes.py @@ -8,14 +8,14 @@ class NodeListIdx(Sequence): - """List of Node.""" + """List of Node accessible by index.""" def __init__(self, nodes: nodes.Nodes): """Constructs a NodeList from an existing dpf.core.nodes.Nodes object.""" self._nodes = nodes def __getitem__(self, idx: int) -> nodes.Node: - """Delegates to node_by_id() if by_id, otherwise to node_by_index().""" + """Returns a Node at a given index.""" return self._nodes.node_by_index(idx) def __len__(self) -> int: @@ -24,11 +24,21 @@ def __len__(self) -> int: @property def by_id(self) -> NodeListById: + """Returns an equivalent list Accessible by ID.""" return NodeListById(self._nodes) - + + class NodeListById(NodeListIdx): + """List of node accessible by ID.""" + def __init__(self, nodes: nodes.Nodes): + """Constructs a list from an existing core.nodes.Nodes object.""" super().__init__(nodes) def __getitem__(self, id: int) -> nodes.Node: - return self._nodes.node_by_id(id) \ No newline at end of file + """Returns a Node for a given ID.""" + return self._nodes.node_by_id(id) + + def __len__(self) -> int: + """Returns the number of nodes in the list.""" + return self._nodes.n_nodes From 6dca2f8b867a91f106171f8f7d070cbcd180063f Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Tue, 18 Apr 2023 10:21:11 +0200 Subject: [PATCH 44/99] Updated mesh example --- .../05-mesh-exploration.py | 178 +++++++++--------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/examples/01-Detailed-Examples/05-mesh-exploration.py b/examples/01-Detailed-Examples/05-mesh-exploration.py index 0740eaece..e585175af 100644 --- a/examples/01-Detailed-Examples/05-mesh-exploration.py +++ b/examples/01-Detailed-Examples/05-mesh-exploration.py @@ -6,6 +6,11 @@ In this script a static simulation is used as an example to show how to query mesh information such as connectivity, element IDs, element types and so on. """ +from __future__ import annotations + +from ansys.dpf import post +from ansys.dpf.post import examples +from ansys.dpf.post.common import elemental_properties ############################################################################### # Perform required imports @@ -13,9 +18,6 @@ # Perform required imports. # This example uses a supplied file that you can # get by importing the DPF ``examples`` package. -from ansys.dpf import post -from ansys.dpf.post import examples -from ansys.dpf.post.common import elemental_properties ############################################################################### # Get ``Simulation`` object @@ -30,9 +32,8 @@ # print the simulation to get an overview of what's available print(simulation) - -# mesh_info = simulation.mesh_info # TODO: expose MeshSelectionManager? -# # print(mesh_info) +stress_df = simulation.stress(components=["XX"]) +print(stress_df) ############################################################################### # Get the mesh @@ -61,25 +62,17 @@ # or by property values meshes[{"mat": 1, "elshape": 0}].plot() - -# simulation.create_elemental_named_selection() -> Selection -# simulation.create_named_selection(element_ids=[,2,,4], name="my_ns") -> Selection -# simulation.split_mesh_by({"named_selection"="my_ns") -# simulation.save_hfd5() -# mesh.name = "mat_id 1" - -# print(meshes) -# """ -# mesh1: {mat_id=1, thickness=2} -# mesh1: {mat_id=2, thickness=2} -# """ - +############################################################################### +# Retrieve the actual mesh mesh = simulation.mesh +# Plot the mesh +plt = mesh.plot() + ############################################################################### -# Query basic information about the mesh (available) +# Query basic information about the mesh (available in PyDPF-Core) # -------------------------------------- - +# # Node IDs n_ids = mesh.node_ids @@ -89,9 +82,6 @@ # Available named selection names named_selections = mesh.available_named_selections -# Query basic information about the mesh (available in PyDPF-Core) -# -------------------------------------- -# # Number of nodes n_nodes = mesh.num_nodes @@ -105,93 +95,103 @@ mesh_unit = mesh.unit mesh.unit = "mm" -# Get the list of nodes/elements IDs of a given named selection -first_name = named_selections[0] -named_selection = mesh.named_selections[first_name] -print(named_selection) -for k, v in mesh.named_selections.items(): - print(k) - print(v) - -# Named selection setter -assert all( - mesh._core_object.named_selection(first_name) - == mesh.named_selections[first_name].ids -) -mesh.named_selections[first_name].ids = [1, 2, 3] -assert all(mesh._core_object.named_selection(first_name).ids == [1, 2, 3]) - +print(n_ids) +print(e_ids) +print(named_selections) +print(n_nodes) +print(n_elements) -# Plot the mesh -plt = mesh.plot() +############################################################################### +# Get Named Selections +# -------------------- +ns_list = mesh.available_named_selections +first_key = ns_list[0] +named_selection = mesh.named_selections[first_key] -# # ####################################### -# # Manipulating elements +for k in mesh.named_selections.keys(): + print(k) +for v in mesh.named_selections.values(): + print(v) +for k, v in mesh.named_selections.items(): + print(f"{k} = {v}") +############################################################################### +# Get elements +# ------------ +# # Get an element by ID -el_0 = mesh.elements[1] +el_by_id = mesh.elements.by_id[1] # Get an element by index -el_0 = mesh.ielements[0] -el_id = mesh.ielements[0].id - -# Adding elements to the mesh -# Not mutable for now +index = el_by_id.index +print(mesh.elements[index]) +############################################################################### +# Element Types and Materials +# # Get the element types -mesh.elements.types -print(mesh.elements.types[el_id]) +el_types = mesh.element_types +print(el_types) # Get the materials -# e_materials = mesh._core_object.elements.materials_field -# -# # Get the elemental connectivity -# connectivity = mesh._core_object.elements.connectivities_field -# -# # ############################################## -# # Query information about one particular element +e_materials = mesh.materials +print(e_materials) + +############################################################################### +# Query information about one particular element # # Get the nodes of an element mesh.elements[1].nodes -# e_nodes = mesh._core_object.elements.element_by_id(1).nodes + # Get the node IDs of an element mesh.elements[1].node_ids -# e_node_ids = mesh._core_object.elements.element_by_id(1).node_ids + # Get the nodes of an element mesh.elements[1].n_nodes -# e_n_node = mesh._core_object.elements.element_by_id(1).n_nodes -# + # Get the type of the element -mesh.elements[1].type -# e_type = mesh._core_object.elements.element_by_id(1).type -# -> link with element_type Enum and element_property map +mesh.elements[1].type_info +mesh.elements[1].type_id + # Get the shape of the element mesh.elements[1].shape -# e_shape = mesh._core_object.elements.element_by_id(1).shape -# Get the connectivity of the element -mesh.elements[1].connectivity -# e_connectivity = mesh._core_object.elements.element_by_id(1).connectivity -# + +############################################################################### +# Get the elemental connectivity # -# # ################## -# # Manipulating nodes +# get node indices from element index +conn1 = mesh.conn_elem_to_node + +# get node IDs from element index +conn2 = mesh.conn_elem_to_node_id + +el_idx_5 = mesh.elements[5] +# get node IDS from element ID +conn2.by_id[el_idx_5.id] + +############################################################################### +# Get nodes +# --------- # # Get a node by ID -node_by_id = mesh.nodes[1] -# node_by_id = mesh._core_object.nodes.node_by_id(1) +node_by_id = mesh.nodes.by_id[1] + # Get a node by index -node_by_index = mesh.inodes[0] -# node_by_index = mesh._core_object.nodes.node_by_index(0) -# -# # Get the coordinates of all nodes -# coordinates = mesh._core_object.nodes.coordinates_field -# -# # ########################################### -# # Query information about one particular node -# +node_by_index = mesh.nodes[0] + +# Get the coordinates of all nodes +print(mesh.coordinates) + +############################################################################### +# Query information about one particular node + # Coordinates -coords = mesh.nodes[1].coordinates -# coor = mesh._core_object.nodes.node_by_id(1).coordinates -# # Nodal connectivity -conn = mesh.nodes[1].nodal_connectivity -# conn = mesh._core_object.nodes.node_by_id(1).nodal_connectivity +mesh.nodes[1].coordinates + +# Get Nodal connectivity +conn3 = mesh.conn_node_to_elem +conn4 = mesh.conn_node_to_elem_id + +# elements IDs from node index +conn4[0] + From befe782e0657ac5928d902017222df4695dbd34c Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Tue, 18 Apr 2023 14:34:30 +0200 Subject: [PATCH 45/99] minor changes --- .../05-mesh-exploration.py | 81 +++++++++++-------- src/ansys/dpf/post/connectivity.py | 7 +- src/ansys/dpf/post/elements.py | 23 ------ src/ansys/dpf/post/fields_container.py | 2 +- 4 files changed, 54 insertions(+), 59 deletions(-) diff --git a/examples/01-Detailed-Examples/05-mesh-exploration.py b/examples/01-Detailed-Examples/05-mesh-exploration.py index e585175af..afcce4bda 100644 --- a/examples/01-Detailed-Examples/05-mesh-exploration.py +++ b/examples/01-Detailed-Examples/05-mesh-exploration.py @@ -39,40 +39,13 @@ # Get the mesh # ------------ -# Split the global mesh according to mesh properties -meshes = simulation.split_mesh_by_properties( - properties=[elemental_properties.material, elemental_properties.element_shape] -) -meshes.plot() - -# Split the global mesh and select meshes for specific property values -print(meshes) -meshes = simulation.split_mesh_by_properties( - properties={ - elemental_properties.material: 1, - elemental_properties.element_shape: [0, 1], - } -) - -# Mesh -meshes.plot() - -# Select a specific Mesh in the Meshes, by index -meshes[1].plot() -# or by property values -meshes[{"mat": 1, "elshape": 0}].plot() - -############################################################################### # Retrieve the actual mesh mesh = simulation.mesh -# Plot the mesh -plt = mesh.plot() - ############################################################################### # Query basic information about the mesh (available in PyDPF-Core) # -------------------------------------- -# + # Node IDs n_ids = mesh.node_ids @@ -118,7 +91,7 @@ ############################################################################### # Get elements # ------------ -# + # Get an element by ID el_by_id = mesh.elements.by_id[1] @@ -128,7 +101,8 @@ ############################################################################### # Element Types and Materials -# +# --------------------------- + # Get the element types el_types = mesh.element_types print(el_types) @@ -139,7 +113,8 @@ ############################################################################### # Query information about one particular element -# +# ---------------------------------------------- + # Get the nodes of an element mesh.elements[1].nodes @@ -158,7 +133,8 @@ ############################################################################### # Get the elemental connectivity -# +# ------------------------------ + # get node indices from element index conn1 = mesh.conn_elem_to_node @@ -172,7 +148,7 @@ ############################################################################### # Get nodes # --------- -# + # Get a node by ID node_by_id = mesh.nodes.by_id[1] @@ -184,6 +160,7 @@ ############################################################################### # Query information about one particular node +# ------------------------------------------- # Coordinates mesh.nodes[1].coordinates @@ -192,6 +169,42 @@ conn3 = mesh.conn_node_to_elem conn4 = mesh.conn_node_to_elem_id +print(mesh.nodes[0]) + +# elements indices to elem_id +print(conn3[0]) + # elements IDs from node index -conn4[0] +print(conn4[0]) + +############################################################################### +# Splitting into meshes +# -------- + +# Plot the mesh +mesh.plot() + +# Split the global mesh according to mesh properties +meshes = simulation.split_mesh_by_properties( + properties=[elemental_properties.material, elemental_properties.element_shape] +) +meshes.plot() + +# Split the global mesh and select meshes for specific property values +print(meshes) +meshes = simulation.split_mesh_by_properties( + properties={ + elemental_properties.material: 1, + elemental_properties.element_shape: [0, 1], + } +) + +# Mesh +meshes.plot() + +# Select a specific Mesh in the Meshes, by index +meshes[1].plot() +# or by property values +meshes[{"mat": 1, "elshape": 0}].plot() + diff --git a/src/ansys/dpf/post/connectivity.py b/src/ansys/dpf/post/connectivity.py index 60ea5f9f1..54a34ea00 100644 --- a/src/ansys/dpf/post/connectivity.py +++ b/src/ansys/dpf/post/connectivity.py @@ -28,6 +28,8 @@ def __init__(self, field: PropertyField, scoping: Scoping, mode: Mode): self._mode = mode self._scoping = scoping + self.local_scoping = None + def __getitem__(self, key: int) -> List[int]: """Returns a list of indexes or IDs for a given index or ID, see Mode Enum.""" if self._mode == Mode.IDS_FROM_IDX: @@ -66,7 +68,10 @@ def by_id(self) -> ConnectivityList: def _to_ids(self, indices) -> List[int]: """Helper method to convert a list of indexes into a list of IDs.""" - to_id = self._scoping.id + if not self.local_scoping: + self.local_scoping = self._scoping.as_local_scoping() + + to_id = self.local_scoping.id return list(map(to_id, indices)) def __len__(self) -> int: diff --git a/src/ansys/dpf/post/elements.py b/src/ansys/dpf/post/elements.py index af7fce427..32d21b44f 100644 --- a/src/ansys/dpf/post/elements.py +++ b/src/ansys/dpf/post/elements.py @@ -135,29 +135,6 @@ def by_id(self) -> ElementListById: """Returns an equivalent list accessible with ID instead of index.""" return ElementListById(self._elements) - @property - def types(self) -> post.DataFrame: - """Returns mapping of element id to corresponding type.""" - field: dpf.Field = self._elements.element_types_field - label = "el_type_id" - fields_container = PropertyFieldsContainer() - fields_container.add_field(label_space={}, field=field) - - return post.DataFrame( - data=fields_container, - index=index.MultiIndex( - indexes=[ - index.MeshIndex( - location=locations.elemental, - scoping=self._elements.scoping, - fc=fields_container, - ) - ] - ), - columns=index.MultiIndex(indexes=[index.ResultsIndex(values=[label])]), - ) - - class ElementListById(ElementListIdx): """Wrapper class for accessing Elements by ID instead of index.""" diff --git a/src/ansys/dpf/post/fields_container.py b/src/ansys/dpf/post/fields_container.py index 2b242ad9f..06676cc45 100644 --- a/src/ansys/dpf/post/fields_container.py +++ b/src/ansys/dpf/post/fields_container.py @@ -47,7 +47,7 @@ def __init__(self, fields_container=None, server=None): self.label_spaces = [] self.ids = [] - # fields_container copy + # PropertyFieldsContainer copy if fields_container is not None: self._labels = copy.deepcopy(fields_container.labels) self.scopings = copy.deepcopy(fields_container.scopings) From 5d566991a8147ba1e0132fbd04e7d244939bc68b Mon Sep 17 00:00:00 2001 From: ansys-akarcher <128720408+ansys-akarcher@users.noreply.github.com> Date: Mon, 24 Apr 2023 17:42:16 +0200 Subject: [PATCH 46/99] Apply suggestions from code review Co-authored-by: Paul Profizi <100710998+PProfizi@users.noreply.github.com> --- .../05-mesh-exploration.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/examples/01-Detailed-Examples/05-mesh-exploration.py b/examples/01-Detailed-Examples/05-mesh-exploration.py index afcce4bda..b67944c49 100644 --- a/examples/01-Detailed-Examples/05-mesh-exploration.py +++ b/examples/01-Detailed-Examples/05-mesh-exploration.py @@ -43,7 +43,7 @@ mesh = simulation.mesh ############################################################################### -# Query basic information about the mesh (available in PyDPF-Core) +# Query basic information about the mesh # -------------------------------------- # Node IDs @@ -116,20 +116,20 @@ # ---------------------------------------------- # Get the nodes of an element -mesh.elements[1].nodes +mesh.elements[0].nodes # Get the node IDs of an element -mesh.elements[1].node_ids +mesh.elements[0].node_ids # Get the nodes of an element -mesh.elements[1].n_nodes +mesh.elements[0].n_nodes # Get the type of the element -mesh.elements[1].type_info -mesh.elements[1].type_id +mesh.elements[0].type_info +mesh.elements[0].type_id # Get the shape of the element -mesh.elements[1].shape +mesh.elements[0].shape ############################################################################### # Get the elemental connectivity @@ -179,7 +179,7 @@ ############################################################################### # Splitting into meshes -# -------- +# --------------------- # Plot the mesh mesh.plot() @@ -199,11 +199,10 @@ } ) -# Mesh meshes.plot() # Select a specific Mesh in the Meshes, by index -meshes[1].plot() +meshes[0].plot() # or by property values meshes[{"mat": 1, "elshape": 0}].plot() From 4eba4111aed34a48f4522cdaac26e98b58e0b889 Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Tue, 25 Apr 2023 12:31:13 +0200 Subject: [PATCH 47/99] Implemented suggestions --- .../05-mesh-exploration.py | 26 +++-- src/ansys/dpf/post/connectivity.py | 97 ++++++++++++------- src/ansys/dpf/post/elements.py | 38 ++++++-- src/ansys/dpf/post/mesh.py | 37 +++---- src/ansys/dpf/post/named_selection.py | 2 +- src/ansys/dpf/post/nodes.py | 31 +++++- 6 files changed, 156 insertions(+), 75 deletions(-) diff --git a/examples/01-Detailed-Examples/05-mesh-exploration.py b/examples/01-Detailed-Examples/05-mesh-exploration.py index b67944c49..41a3e912d 100644 --- a/examples/01-Detailed-Examples/05-mesh-exploration.py +++ b/examples/01-Detailed-Examples/05-mesh-exploration.py @@ -6,11 +6,6 @@ In this script a static simulation is used as an example to show how to query mesh information such as connectivity, element IDs, element types and so on. """ -from __future__ import annotations - -from ansys.dpf import post -from ansys.dpf.post import examples -from ansys.dpf.post.common import elemental_properties ############################################################################### # Perform required imports @@ -18,6 +13,9 @@ # Perform required imports. # This example uses a supplied file that you can # get by importing the DPF ``examples`` package. +from ansys.dpf import post +from ansys.dpf.post import examples +from ansys.dpf.post.common import elemental_properties ############################################################################### # Get ``Simulation`` object @@ -32,8 +30,6 @@ # print the simulation to get an overview of what's available print(simulation) -stress_df = simulation.stress(components=["XX"]) -print(stress_df) ############################################################################### # Get the mesh @@ -53,7 +49,7 @@ e_ids = mesh.element_ids # Available named selection names -named_selections = mesh.available_named_selections +named_selections = mesh.named_selections.keys() # Number of nodes n_nodes = mesh.num_nodes @@ -77,7 +73,7 @@ ############################################################################### # Get Named Selections # -------------------- -ns_list = mesh.available_named_selections +ns_list = mesh.named_selections.keys() first_key = ns_list[0] named_selection = mesh.named_selections[first_key] @@ -122,11 +118,11 @@ mesh.elements[0].node_ids # Get the nodes of an element -mesh.elements[0].n_nodes +mesh.elements[0].num_nodes # Get the type of the element mesh.elements[0].type_info -mesh.elements[0].type_id +mesh.elements[0].type # Get the shape of the element mesh.elements[0].shape @@ -136,10 +132,10 @@ # ------------------------------ # get node indices from element index -conn1 = mesh.conn_elem_to_node +conn1 = mesh.element_to_node_connectivity # get node IDs from element index -conn2 = mesh.conn_elem_to_node_id +conn2 = mesh.element_to_node_ids_connectivity el_idx_5 = mesh.elements[5] # get node IDS from element ID @@ -166,8 +162,8 @@ mesh.nodes[1].coordinates # Get Nodal connectivity -conn3 = mesh.conn_node_to_elem -conn4 = mesh.conn_node_to_elem_id +conn3 = mesh.node_to_element_connectivity +conn4 = mesh.node_to_element_ids_connectivity print(mesh.nodes[0]) diff --git a/src/ansys/dpf/post/connectivity.py b/src/ansys/dpf/post/connectivity.py index 54a34ea00..ecafa90c7 100644 --- a/src/ansys/dpf/post/connectivity.py +++ b/src/ansys/dpf/post/connectivity.py @@ -2,7 +2,7 @@ from __future__ import annotations -from collections.abc import Sequence +from collections.abc import Collection, Iterator from enum import Enum from typing import List @@ -10,19 +10,31 @@ from ansys.dpf.core.scoping import Scoping -class Mode(Enum): +class ReturnMode(Enum): """Enum made for internal use, to dictate the behavior of ConnectivityList.""" - - IDS_FROM_IDX = 1 - IDS_FROM_ID = 2 - IDX_FROM_IDX = 3 - IDX_FROM_ID = 4 - - -class ConnectivityList(Sequence): + IDS = 1 + IDX = 2 + +class ConnectivityListIterator(Iterator): + def __init__(self, conn_list: ConnectivityList): + self._conn_list = conn_list + self._idx = 0 + + def __next__(self) -> Any: + if self._idx >= self._conn_list.__len__(): + raise StopIteration + + ret = self._conn_list[self._idx] + self._idx += 1 + return ret + + def __iter__(self) -> ConnectivityListIterator: + return ConnectivityListIdx(self._conn_list) + +class ConnectivityListIdx(Collection): """Very basic wrapper around elemental and nodal connectivities fields.""" - def __init__(self, field: PropertyField, scoping: Scoping, mode: Mode): + def __init__(self, field: PropertyField, scoping: Scoping, mode: ReturnMode): """Constructs a ConnectivityList by wrapping given PropertyField.""" self._field = field self._mode = mode @@ -32,40 +44,25 @@ def __init__(self, field: PropertyField, scoping: Scoping, mode: Mode): def __getitem__(self, key: int) -> List[int]: """Returns a list of indexes or IDs for a given index or ID, see Mode Enum.""" - if self._mode == Mode.IDS_FROM_IDX: + if self._mode == ReturnMode.IDS: return self._get_ids_from_idx(key) - elif self._mode == Mode.IDS_FROM_ID: - return self._get_ids_from_id(key) - elif self._mode == Mode.IDX_FROM_IDX: + elif self._mode == ReturnMode.IDX: return self._get_idx_from_idx(key) - elif self._mode == Mode.IDX_FROM_ID: - return self._get_idx_from_id(key) + raise ValueError(f"ReturnMode has an incorrect value") def _get_ids_from_idx(self, idx: int) -> List[int]: """Helper method to retrieve list of IDs from a given index.""" - return self._to_ids(self._field.get_entity_data(idx)) - - def _get_ids_from_id(self, id: int) -> List[int]: - """Helper method to retrieve list of IDs from a given ID.""" - return self._to_ids(self._field.get_entity_data_by_id(id)) + return self._to_ids(self._get_idx_from_idx(idx)) def _get_idx_from_idx(self, idx: int) -> List[int]: """Helper method to retrieve list of indexes from a given index.""" return self._field.get_entity_data(idx) - def _get_idx_from_id(self, id: int) -> List[int]: - """Helper method to retrieve list of indexes from a given ID.""" - return self._field.get_entity_data_by_id(id) - @property - def by_id(self) -> ConnectivityList: + def by_id(self) -> ConnectivityListById: """Returns an equivalent list which accepts an ID instead of an index in __getitem__.""" - if self._mode == Mode.IDS_FROM_IDX: - return ConnectivityList(self._field, self._scoping, Mode.IDS_FROM_ID) - elif self._mode == Mode.IDX_FROM_IDX: - return ConnectivityList(self._field, self._scoping, Mode.IDX_FROM_ID) - return self - + return ConnectivityListById(self._field, self._scoping, self._mode) + def _to_ids(self, indices) -> List[int]: """Helper method to convert a list of indexes into a list of IDs.""" if not self.local_scoping: @@ -73,7 +70,41 @@ def _to_ids(self, indices) -> List[int]: to_id = self.local_scoping.id return list(map(to_id, indices)) + + def __contains__(self, l: List[int]) -> bool: + raise NotImplementedError + + def __iter__(self) -> ConnectivityListIterator: + return ConnectivityListIterator(self) def __len__(self) -> int: """Returns the number of entities.""" return len(self._field._get_data_pointer()) + +class ConnectivityListById(ConnectivityListIdx): + def __init__(self, field: PropertyField, scoping: Scoping, mode: Mode): + super().__init__(field, scoping, mode) + + def __getitem__(self, key: int) -> List[int]: + if self._mode == ReturnMode.IDS: + return self._get_ids_from_id(key) + elif self._mode == ReturnMode.IDX: + return self._get_idx_from_id(key) + raise ValueError(f"ReturnMode has an incorrect value") + + def _get_ids_from_id(self, id: int) -> List[int]: + """Helper method to retrieve list of IDs from a given ID.""" + return self._to_ids(self._get_idx_from_id(id)) + + def _get_idx_from_id(self, id: int) -> List[int]: + """Helper method to retrieve list of indexes from a given ID.""" + return self._field.get_entity_data_by_id(id) + + def __contains__(self, l: List[int]) -> bool: + raise NotImplementedError + + def __iter__(self) -> ConnectivityListIterator: + return super().__iter__() + + def __len__(self) -> int: + return len(self._field._get_data_pointer()) \ No newline at end of file diff --git a/src/ansys/dpf/post/elements.py b/src/ansys/dpf/post/elements.py index 32d21b44f..e16c2ef0f 100644 --- a/src/ansys/dpf/post/elements.py +++ b/src/ansys/dpf/post/elements.py @@ -2,16 +2,13 @@ from __future__ import annotations -from collections.abc import Sequence +from collections.abc import Collection, Iterator from typing import List, Union import ansys.dpf.core as dpf import ansys.dpf.core.elements as elements import ansys.dpf.core.nodes as nodes # noqa: F401 -import ansys.dpf.post as post -from ansys.dpf.post import index, locations -from ansys.dpf.post.fields_container import PropertyFieldsContainer class ElementType(dpf.ElementDescriptor): @@ -82,7 +79,7 @@ def nodes(self) -> List[nodes.Node]: return self._resolve().nodes @property - def n_nodes(self) -> int: + def num_nodes(self) -> int: """See :py:meth:`ansys.dpf.core.elements.Element.n_nodes`.""" return self._resolve().n_nodes @@ -92,7 +89,7 @@ def type_info(self) -> ElementType: return ElementType(self._resolve().type.value) @property - def type_id(self) -> int: + def type(self) -> int: """Returns the ID of the Element Type.""" return self._resolve().type.value @@ -114,8 +111,23 @@ def __str__(self) -> str: """Returns string representation of an Element.""" return self._resolve().__str__() +class ElementListIterator(Iterator): + def __init__(self, el_list: ElementListIdx): + self._el_list = el_list + self._idx = 0 -class ElementListIdx(Sequence): + def __next__(self) -> Element: + if self._idx >= self._el_list.__len__(): + raise StopIteration + + ret = self._el_list[self._idx] + self._idx += 1 + return ret + + def __iter__(self) -> Iterator: + return ElementListIterator(self._el_list) + +class ElementListIdx(Collection): """List of Elements.""" def __init__(self, elements: elements.Elements): @@ -125,6 +137,12 @@ def __init__(self, elements: elements.Elements): def __getitem__(self, idx: int) -> Element: """Delegates to element_by_id() if by_id, otherwise to element_by_index().""" return Element(self._elements, idx) + + def __contains__(self, el: Element) -> bool: + return el.index >= 0 and el.index < self.__len__() + + def __iter__(self) -> ElementListIterator: + return ElementListIterator(self) def __len__(self) -> int: """Returns the number of elements in the list.""" @@ -147,6 +165,12 @@ def __getitem__(self, id: int) -> Element: idx = self._elements.scoping.index(id) return super().__getitem__(idx) + def __contains__(self, el: Element): + return el.id in self._elements.scoping.ids + + def __iter__(self) -> ElementListIterator: + return super().__iter__() + def __len__(self) -> int: """Returns the number of elements in the list.""" return self._elements.n_elements diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index d4dcc647b..39e6c9c83 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -10,13 +10,15 @@ import ansys.dpf.core as dpf +from ansys.dpf.core.nodes import Node + import ansys.dpf.post as post from ansys.dpf.post import index, locations -from ansys.dpf.post.connectivity import ConnectivityList, Mode -from ansys.dpf.post.elements import ElementListIdx +from ansys.dpf.post.connectivity import ConnectivityListIdx, ReturnMode +from ansys.dpf.post.elements import ElementListIdx, Element +from ansys.dpf.post.nodes import NodeListIdx from ansys.dpf.post.fields_container import PropertyFieldsContainer from ansys.dpf.post.named_selection import NamedSelectionsDict -from ansys.dpf.post.nodes import NodeListIdx class Mesh: @@ -30,14 +32,9 @@ def __str__(self): """String representation of this class.""" return str(self._meshed_region).replace("Meshed Region", "Mesh") - @property - def available_named_selections(self) -> List[str]: - """Returns the list of name of available named selections in the mesh.""" - return self._meshed_region.available_named_selections - @property def named_selections(self) -> NamedSelectionsDict: - """Returns the list of named selections in the mesh.""" + """Returns a mapping of scopings""" return NamedSelectionsDict(self._meshed_region) @property @@ -64,12 +61,18 @@ def num_elements(self) -> int: def elements(self) -> ElementListIdx: """Returns a list of elements indexed by ID.""" return ElementListIdx(self._meshed_region.elements) + + def get_element_by_id(self, id: int) -> Element: + return self.elements.by_id[id] @property def nodes(self) -> NodeListIdx: """Returns a list of nodes indexed by ID.""" return NodeListIdx(self._meshed_region.nodes) + def get_node_by_id(self, id: int) -> Node: + return self.nodes.by_id[id] + @property def element_types(self) -> post.DataFrame: """Returns a DataFrame containing element types ID.""" @@ -115,32 +118,32 @@ def materials(self) -> post.DataFrame: ) @property - def conn_elem_to_node_id(self): + def element_to_node_ids_connectivity(self): """Returns a list of Node ID for a given Element index.""" conn_field = self._meshed_region.elements.connectivities_field nodes_scoping = self._meshed_region.nodes.scoping - return ConnectivityList(conn_field, nodes_scoping, Mode.IDS_FROM_IDX) + return ConnectivityListIdx(conn_field, nodes_scoping, ReturnMode.IDS) @property - def conn_node_to_elem_id(self): + def node_to_element_ids_connectivity(self): """Returns a list of Element ID for a given Node index.""" conn_field = self._meshed_region.nodes.nodal_connectivity_field elems_scoping = self._meshed_region.elements.scoping - return ConnectivityList(conn_field, elems_scoping, Mode.IDS_FROM_IDX) + return ConnectivityListIdx(conn_field, elems_scoping, ReturnMode.IDS) @property - def conn_elem_to_node(self): + def element_to_node_connectivity(self): """Returns a list of Node index for a given Element index.""" conn_field = self._meshed_region.elements.connectivities_field nodes_scoping = self._meshed_region.nodes.scoping - return ConnectivityList(conn_field, nodes_scoping, Mode.IDX_FROM_IDX) + return ConnectivityListIdx(conn_field, nodes_scoping, ReturnMode.IDX) @property - def conn_node_to_elem(self): + def node_to_element_connectivity(self): """Returns a list of Element index for a given Node index.""" conn_field = self._meshed_region.nodes.nodal_connectivity_field elems_scoping = self._meshed_region.elements.scoping - return ConnectivityList(conn_field, elems_scoping, Mode.IDX_FROM_IDX) + return ConnectivityListIdx(conn_field, elems_scoping, ReturnMode.IDX) @property def unit(self) -> str: diff --git a/src/ansys/dpf/post/named_selection.py b/src/ansys/dpf/post/named_selection.py index f3ad89dae..3dd67e870 100644 --- a/src/ansys/dpf/post/named_selection.py +++ b/src/ansys/dpf/post/named_selection.py @@ -120,4 +120,4 @@ def as_local_scoping(self) -> NamedSelection: def __repr__(self) -> str: """Pretty print string of the NamedSelection.""" - return f"NamedSelection '{self.name}' with scoping {self._scoping.__str__()}" + return f"NamedSelection '{self.name}'\n with {self._scoping.__str__()}" diff --git a/src/ansys/dpf/post/nodes.py b/src/ansys/dpf/post/nodes.py index 33eb476c4..c9cfd7edf 100644 --- a/src/ansys/dpf/post/nodes.py +++ b/src/ansys/dpf/post/nodes.py @@ -2,12 +2,27 @@ from __future__ import annotations -from collections.abc import Sequence +from collections.abc import Collection, Iterator import ansys.dpf.core.nodes as nodes +class NodeListIterator(Iterator): + def __init__(self, nodes: NodeListIdx): + self._nodes = nodes + self._idx = 0 + + def __next__(self) -> nodes.Node: + if self._idx >= self._nodes.__len__(): + raise StopIteration + + ret = self._nodes[self._idx] + self._idx += 1 + return ret + + def __iter__(self) -> NodeListIterator: + return NodeListIterator(self._nodes) -class NodeListIdx(Sequence): +class NodeListIdx(Collection): """List of Node accessible by index.""" def __init__(self, nodes: nodes.Nodes): @@ -18,6 +33,12 @@ def __getitem__(self, idx: int) -> nodes.Node: """Returns a Node at a given index.""" return self._nodes.node_by_index(idx) + def __contains__(self, node: nodes.Node) -> bool: + return node.index >= 0 and node.index < self.__len__() + + def __iter__(self) -> NodeListIterator: + return NodeListIterator(self) + def __len__(self) -> int: """Returns the number of nodes in the list.""" return self._nodes.n_nodes @@ -39,6 +60,12 @@ def __getitem__(self, id: int) -> nodes.Node: """Returns a Node for a given ID.""" return self._nodes.node_by_id(id) + def __contains__(self, node: nodes.Node) -> bool: + return node.id in self._nodes.scoping.ids + + def __iter__(self) -> NodeListIterator: + return super().__iter__() + def __len__(self) -> int: """Returns the number of nodes in the list.""" return self._nodes.n_nodes From e81da719186fcd0db91a6cdb0d6d2d4c7b43ccb9 Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Tue, 25 Apr 2023 16:25:18 +0200 Subject: [PATCH 48/99] Added mesh related tests --- tests/test_mesh.py | 61 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/tests/test_mesh.py b/tests/test_mesh.py index abb8f53ba..9793a7f8d 100644 --- a/tests/test_mesh.py +++ b/tests/test_mesh.py @@ -21,17 +21,72 @@ def test_mesh_node_ids(mesh): assert len(n_ids) == 81 assert all([isinstance(i, (int, np.integer)) for i in n_ids]) - def test_mesh_element_ids(mesh): e_ids = mesh.element_ids assert len(e_ids) == 8 assert all([isinstance(i, (int, np.integer)) for i in e_ids]) +def test_mesh_num(mesh): + assert mesh.num_nodes == 81 + assert mesh.num_elements == 8 -def test_mesh_available_named_selections(mesh): - ns = mesh.available_named_selections +def test_mesh_named_selections(mesh): + ns = mesh.named_selections.keys() assert len(ns) == 1 assert all([isinstance(n, str) for n in ns]) + assert ns[0] == '_FIXEDSU' + assert len(mesh.named_selections[ns[0]].ids) == 21 + +def test_mesh_unit(mesh): + assert mesh.unit == 'm' + +def test_mesh_nodes(mesh): + node_idx_0 = mesh.nodes[0] + node_id_81 = mesh.nodes.by_id[81] + + assert len(mesh.nodes) == 81 + assert node_idx_0.index == 0 + assert node_id_81.id == 81 + assert all(np.isclose(node_idx_0.coordinates, [0.015, 0.045, 0.015])) + assert all(np.isclose(node_id_81.coordinates, [0.03, 0.045, 0.0075])) + assert np.array_equal([node.index for node in mesh.nodes], list(range(81))) + assert mesh.get_node_by_id(node_idx_0.id).index == 0 + +def test_mesh_elements(mesh): + elem_idx_0 = mesh.elements[0] + elem_id_8 = mesh.elements.by_id[8] + + assert len(mesh.elements) == 8 + assert elem_idx_0.index == 0 + assert elem_id_8.id == 8 + assert elem_idx_0.num_nodes == 20 + assert elem_idx_0.type == dpf.element_types.Hex20.value + assert elem_idx_0.shape == 'solid' + assert mesh.get_element_by_id(elem_idx_0.id).index == 0 + +def test_mesh_nodal_connectivity(mesh): + conn_node_get_idx = mesh.node_to_element_connectivity + conn_node_get_id = mesh.node_to_element_ids_connectivity + + node_idx_0 = mesh.nodes[0] + conn_idx = node_idx_0.nodal_connectivity + conn_id = [mesh.elements[idx].id for idx in conn_idx] + + assert len(conn_idx) == len(conn_id) + assert np.array_equal(conn_idx, conn_node_get_idx[0]) + assert np.array_equal(conn_id, conn_node_get_id[0]) + +def test_mesh_elemental_connectivity(mesh): + conn_elem_get_idx = mesh.element_to_node_connectivity + conn_elem_get_id = mesh.element_to_node_ids_connectivity + + elem_idx_0 = mesh.elements[0] + conn_idx = elem_idx_0.connectivity + conn_id = [mesh.nodes[idx].id for idx in conn_idx] + + assert len(conn_idx) == len(conn_id) + assert np.array_equal(conn_idx, conn_elem_get_idx[0]) + assert np.array_equal(conn_id, conn_elem_get_id[0]) def test_mesh_str(mesh): From c9f07a27409d09bb0363148e5de3fef75c04b7cb Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Tue, 25 Apr 2023 16:59:39 +0200 Subject: [PATCH 49/99] pre-commit checks conformance --- .../05-mesh-exploration.py | 4 +- src/ansys/dpf/post/connectivity.py | 40 ++++++++++++++----- src/ansys/dpf/post/elements.py | 17 ++++++-- src/ansys/dpf/post/mesh.py | 11 ++--- src/ansys/dpf/post/nodes.py | 17 ++++++-- tests/test_mesh.py | 16 ++++++-- 6 files changed, 76 insertions(+), 29 deletions(-) diff --git a/examples/01-Detailed-Examples/05-mesh-exploration.py b/examples/01-Detailed-Examples/05-mesh-exploration.py index 41a3e912d..004b7eff0 100644 --- a/examples/01-Detailed-Examples/05-mesh-exploration.py +++ b/examples/01-Detailed-Examples/05-mesh-exploration.py @@ -73,7 +73,7 @@ ############################################################################### # Get Named Selections # -------------------- -ns_list = mesh.named_selections.keys() +ns_list = mesh.named_selections.keys() first_key = ns_list[0] named_selection = mesh.named_selections[first_key] @@ -201,5 +201,3 @@ meshes[0].plot() # or by property values meshes[{"mat": 1, "elshape": 0}].plot() - - diff --git a/src/ansys/dpf/post/connectivity.py b/src/ansys/dpf/post/connectivity.py index ecafa90c7..c915cea21 100644 --- a/src/ansys/dpf/post/connectivity.py +++ b/src/ansys/dpf/post/connectivity.py @@ -12,25 +12,33 @@ class ReturnMode(Enum): """Enum made for internal use, to dictate the behavior of ConnectivityList.""" + IDS = 1 IDX = 2 + class ConnectivityListIterator(Iterator): + """Iterator class for Connectivity Lists.""" + def __init__(self, conn_list: ConnectivityList): + """Constructs an iterator from an existing list.""" self._conn_list = conn_list self._idx = 0 - + def __next__(self) -> Any: + """Returns the next element in the list.""" if self._idx >= self._conn_list.__len__(): raise StopIteration - + ret = self._conn_list[self._idx] self._idx += 1 return ret - + def __iter__(self) -> ConnectivityListIterator: + """Returns a new ConnectivityListIterator.""" return ConnectivityListIdx(self._conn_list) + class ConnectivityListIdx(Collection): """Very basic wrapper around elemental and nodal connectivities fields.""" @@ -43,7 +51,7 @@ def __init__(self, field: PropertyField, scoping: Scoping, mode: ReturnMode): self.local_scoping = None def __getitem__(self, key: int) -> List[int]: - """Returns a list of indexes or IDs for a given index or ID, see Mode Enum.""" + """Returns a list of indexes or IDs for a given index, see ReturnMode Enum.""" if self._mode == ReturnMode.IDS: return self._get_ids_from_idx(key) elif self._mode == ReturnMode.IDX: @@ -62,7 +70,7 @@ def _get_idx_from_idx(self, idx: int) -> List[int]: def by_id(self) -> ConnectivityListById: """Returns an equivalent list which accepts an ID instead of an index in __getitem__.""" return ConnectivityListById(self._field, self._scoping, self._mode) - + def _to_ids(self, indices) -> List[int]: """Helper method to convert a list of indexes into a list of IDs.""" if not self.local_scoping: @@ -70,28 +78,35 @@ def _to_ids(self, indices) -> List[int]: to_id = self.local_scoping.id return list(map(to_id, indices)) - + def __contains__(self, l: List[int]) -> bool: + """Not implemented.""" raise NotImplementedError - + def __iter__(self) -> ConnectivityListIterator: + """Returns an iterator object on the list.""" return ConnectivityListIterator(self) def __len__(self) -> int: """Returns the number of entities.""" return len(self._field._get_data_pointer()) + class ConnectivityListById(ConnectivityListIdx): + """Connectivity List indexed by ID.""" + def __init__(self, field: PropertyField, scoping: Scoping, mode: Mode): + """Constructs a Connectivity list from a given PropertyField.""" super().__init__(field, scoping, mode) - + def __getitem__(self, key: int) -> List[int]: + """Returns a list of indexes or IDs for a given ID, see ReturnMode Enum.""" if self._mode == ReturnMode.IDS: return self._get_ids_from_id(key) elif self._mode == ReturnMode.IDX: return self._get_idx_from_id(key) raise ValueError(f"ReturnMode has an incorrect value") - + def _get_ids_from_id(self, id: int) -> List[int]: """Helper method to retrieve list of IDs from a given ID.""" return self._to_ids(self._get_idx_from_id(id)) @@ -99,12 +114,15 @@ def _get_ids_from_id(self, id: int) -> List[int]: def _get_idx_from_id(self, id: int) -> List[int]: """Helper method to retrieve list of indexes from a given ID.""" return self._field.get_entity_data_by_id(id) - + def __contains__(self, l: List[int]) -> bool: + """Not Implemented.""" raise NotImplementedError def __iter__(self) -> ConnectivityListIterator: + """Returns an iterator object on the list.""" return super().__iter__() def __len__(self) -> int: - return len(self._field._get_data_pointer()) \ No newline at end of file + """Returns the number of entities.""" + return len(self._field._get_data_pointer()) diff --git a/src/ansys/dpf/post/elements.py b/src/ansys/dpf/post/elements.py index e16c2ef0f..ef9830cc7 100644 --- a/src/ansys/dpf/post/elements.py +++ b/src/ansys/dpf/post/elements.py @@ -10,7 +10,6 @@ import ansys.dpf.core.nodes as nodes # noqa: F401 - class ElementType(dpf.ElementDescriptor): """Wrapper type to instantiate an ElementDescriptor from an int.""" @@ -111,12 +110,17 @@ def __str__(self) -> str: """Returns string representation of an Element.""" return self._resolve().__str__() + class ElementListIterator(Iterator): + """Iterator class for the ElementList.""" + def __init__(self, el_list: ElementListIdx): + """Constructs an Iterator from an element list.""" self._el_list = el_list self._idx = 0 def __next__(self) -> Element: + """Returns the next Element in the list.""" if self._idx >= self._el_list.__len__(): raise StopIteration @@ -125,8 +129,10 @@ def __next__(self) -> Element: return ret def __iter__(self) -> Iterator: + """Returns a new Iterator object.""" return ElementListIterator(self._el_list) + class ElementListIdx(Collection): """List of Elements.""" @@ -137,11 +143,13 @@ def __init__(self, elements: elements.Elements): def __getitem__(self, idx: int) -> Element: """Delegates to element_by_id() if by_id, otherwise to element_by_index().""" return Element(self._elements, idx) - + def __contains__(self, el: Element) -> bool: + """Checks if the given element in the list.""" return el.index >= 0 and el.index < self.__len__() def __iter__(self) -> ElementListIterator: + """Returns an Iterator object on the list.""" return ElementListIterator(self) def __len__(self) -> int: @@ -153,6 +161,7 @@ def by_id(self) -> ElementListById: """Returns an equivalent list accessible with ID instead of index.""" return ElementListById(self._elements) + class ElementListById(ElementListIdx): """Wrapper class for accessing Elements by ID instead of index.""" @@ -166,9 +175,11 @@ def __getitem__(self, id: int) -> Element: return super().__getitem__(idx) def __contains__(self, el: Element): + """Checks if the given element is in the list.""" return el.id in self._elements.scoping.ids - + def __iter__(self) -> ElementListIterator: + """Returns an iterator object on the list.""" return super().__iter__() def __len__(self) -> int: diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index 39e6c9c83..e36e9d0be 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -9,16 +9,15 @@ from typing import List import ansys.dpf.core as dpf - from ansys.dpf.core.nodes import Node import ansys.dpf.post as post from ansys.dpf.post import index, locations from ansys.dpf.post.connectivity import ConnectivityListIdx, ReturnMode -from ansys.dpf.post.elements import ElementListIdx, Element -from ansys.dpf.post.nodes import NodeListIdx +from ansys.dpf.post.elements import Element, ElementListIdx from ansys.dpf.post.fields_container import PropertyFieldsContainer from ansys.dpf.post.named_selection import NamedSelectionsDict +from ansys.dpf.post.nodes import NodeListIdx class Mesh: @@ -34,7 +33,7 @@ def __str__(self): @property def named_selections(self) -> NamedSelectionsDict: - """Returns a mapping of scopings""" + """Returns a mapping of scopings.""" return NamedSelectionsDict(self._meshed_region) @property @@ -61,8 +60,9 @@ def num_elements(self) -> int: def elements(self) -> ElementListIdx: """Returns a list of elements indexed by ID.""" return ElementListIdx(self._meshed_region.elements) - + def get_element_by_id(self, id: int) -> Element: + """Returns an element in the mesh from a given ID.""" return self.elements.by_id[id] @property @@ -71,6 +71,7 @@ def nodes(self) -> NodeListIdx: return NodeListIdx(self._meshed_region.nodes) def get_node_by_id(self, id: int) -> Node: + """Returns a node of the mesh from a given ID.""" return self.nodes.by_id[id] @property diff --git a/src/ansys/dpf/post/nodes.py b/src/ansys/dpf/post/nodes.py index c9cfd7edf..e8ad8e639 100644 --- a/src/ansys/dpf/post/nodes.py +++ b/src/ansys/dpf/post/nodes.py @@ -6,22 +6,29 @@ import ansys.dpf.core.nodes as nodes + class NodeListIterator(Iterator): + """Iterator object for NodeListIdx.""" + def __init__(self, nodes: NodeListIdx): + """Constructs an iterator from a Node List.""" self._nodes = nodes self._idx = 0 def __next__(self) -> nodes.Node: + """Returns the next Node in the list.""" if self._idx >= self._nodes.__len__(): - raise StopIteration - + raise StopIteration + ret = self._nodes[self._idx] self._idx += 1 return ret def __iter__(self) -> NodeListIterator: + """Returns a new Iterator object.""" return NodeListIterator(self._nodes) + class NodeListIdx(Collection): """List of Node accessible by index.""" @@ -34,9 +41,11 @@ def __getitem__(self, idx: int) -> nodes.Node: return self._nodes.node_by_index(idx) def __contains__(self, node: nodes.Node) -> bool: + """Checks if given node is in the list.""" return node.index >= 0 and node.index < self.__len__() def __iter__(self) -> NodeListIterator: + """Returns an iterator object on the list.""" return NodeListIterator(self) def __len__(self) -> int: @@ -61,9 +70,11 @@ def __getitem__(self, id: int) -> nodes.Node: return self._nodes.node_by_id(id) def __contains__(self, node: nodes.Node) -> bool: + """Checks if the given node is in the list.""" return node.id in self._nodes.scoping.ids - + def __iter__(self) -> NodeListIterator: + """Returns an iterator object on the list.""" return super().__iter__() def __len__(self) -> int: diff --git a/tests/test_mesh.py b/tests/test_mesh.py index 9793a7f8d..5337a890d 100644 --- a/tests/test_mesh.py +++ b/tests/test_mesh.py @@ -21,24 +21,29 @@ def test_mesh_node_ids(mesh): assert len(n_ids) == 81 assert all([isinstance(i, (int, np.integer)) for i in n_ids]) + def test_mesh_element_ids(mesh): e_ids = mesh.element_ids assert len(e_ids) == 8 assert all([isinstance(i, (int, np.integer)) for i in e_ids]) + def test_mesh_num(mesh): assert mesh.num_nodes == 81 assert mesh.num_elements == 8 + def test_mesh_named_selections(mesh): ns = mesh.named_selections.keys() assert len(ns) == 1 assert all([isinstance(n, str) for n in ns]) - assert ns[0] == '_FIXEDSU' + assert ns[0] == "_FIXEDSU" assert len(mesh.named_selections[ns[0]].ids) == 21 + def test_mesh_unit(mesh): - assert mesh.unit == 'm' + assert mesh.unit == "m" + def test_mesh_nodes(mesh): node_idx_0 = mesh.nodes[0] @@ -52,6 +57,7 @@ def test_mesh_nodes(mesh): assert np.array_equal([node.index for node in mesh.nodes], list(range(81))) assert mesh.get_node_by_id(node_idx_0.id).index == 0 + def test_mesh_elements(mesh): elem_idx_0 = mesh.elements[0] elem_id_8 = mesh.elements.by_id[8] @@ -61,9 +67,10 @@ def test_mesh_elements(mesh): assert elem_id_8.id == 8 assert elem_idx_0.num_nodes == 20 assert elem_idx_0.type == dpf.element_types.Hex20.value - assert elem_idx_0.shape == 'solid' + assert elem_idx_0.shape == "solid" assert mesh.get_element_by_id(elem_idx_0.id).index == 0 + def test_mesh_nodal_connectivity(mesh): conn_node_get_idx = mesh.node_to_element_connectivity conn_node_get_id = mesh.node_to_element_ids_connectivity @@ -76,6 +83,7 @@ def test_mesh_nodal_connectivity(mesh): assert np.array_equal(conn_idx, conn_node_get_idx[0]) assert np.array_equal(conn_id, conn_node_get_id[0]) + def test_mesh_elemental_connectivity(mesh): conn_elem_get_idx = mesh.element_to_node_connectivity conn_elem_get_id = mesh.element_to_node_ids_connectivity @@ -83,7 +91,7 @@ def test_mesh_elemental_connectivity(mesh): elem_idx_0 = mesh.elements[0] conn_idx = elem_idx_0.connectivity conn_id = [mesh.nodes[idx].id for idx in conn_idx] - + assert len(conn_idx) == len(conn_id) assert np.array_equal(conn_idx, conn_elem_get_idx[0]) assert np.array_equal(conn_id, conn_elem_get_id[0]) From 0bde5303cc462912c92a343a354bf62cc8abd4f2 Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Wed, 26 Apr 2023 15:53:19 +0200 Subject: [PATCH 50/99] Added string repr, ElementType composition, Node wrapper --- src/ansys/dpf/post/connectivity.py | 57 +++++++++------ src/ansys/dpf/post/elements.py | 107 +++++++++++++++++++++-------- src/ansys/dpf/post/nodes.py | 64 +++++++++++++++-- 3 files changed, 175 insertions(+), 53 deletions(-) diff --git a/src/ansys/dpf/post/connectivity.py b/src/ansys/dpf/post/connectivity.py index c915cea21..508953a77 100644 --- a/src/ansys/dpf/post/connectivity.py +++ b/src/ansys/dpf/post/connectivity.py @@ -20,7 +20,7 @@ class ReturnMode(Enum): class ConnectivityListIterator(Iterator): """Iterator class for Connectivity Lists.""" - def __init__(self, conn_list: ConnectivityList): + def __init__(self, conn_list: PropertyField): """Constructs an iterator from an existing list.""" self._conn_list = conn_list self._idx = 0 @@ -30,7 +30,7 @@ def __next__(self) -> Any: if self._idx >= self._conn_list.__len__(): raise StopIteration - ret = self._conn_list[self._idx] + ret = self._conn_list.get_entity_data(self._idx) self._idx += 1 return ret @@ -50,12 +50,12 @@ def __init__(self, field: PropertyField, scoping: Scoping, mode: ReturnMode): self.local_scoping = None - def __getitem__(self, key: int) -> List[int]: + def __getitem__(self, idx: int) -> List[int]: """Returns a list of indexes or IDs for a given index, see ReturnMode Enum.""" if self._mode == ReturnMode.IDS: - return self._get_ids_from_idx(key) + return self._get_ids_from_idx(idx) elif self._mode == ReturnMode.IDX: - return self._get_idx_from_idx(key) + return self._get_idx_from_idx(idx) raise ValueError(f"ReturnMode has an incorrect value") def _get_ids_from_idx(self, idx: int) -> List[int]: @@ -85,12 +85,35 @@ def __contains__(self, l: List[int]) -> bool: def __iter__(self) -> ConnectivityListIterator: """Returns an iterator object on the list.""" - return ConnectivityListIterator(self) + return ConnectivityListIterator(self._field) def __len__(self) -> int: """Returns the number of entities.""" return len(self._field._get_data_pointer()) - + + def _short_list(self) -> str: + _str = "[" + if self.__len__() > 3: + _fst = self._field.get_entity_data(0) + _lst = self._field.get_entity_data(self.__len__()-1) + if self._mode == ReturnMode.IDS: + _fst = self._to_ids(_fst) + _lst = self._to_ids(_lst) + _str += f"{_fst}, ..., {_lst}" + else: + conn_list = [self._field.get_entity_data(idx) for idx in range(self.__len__())] + if self._mode == ReturnMode.IDS: + conn_list = list(map(self._to_ids, conn_list)) + _str += ", ".join(map(lambda l: str(l), conn_list)) + _str += "]" + return _str + + def __str__(self) -> str: + return self._short_list() + + def __repr__(self) -> str: + return f"ConnectivityListIdx({self.__str__()},__len__={self.__len__()})" + class ConnectivityListById(ConnectivityListIdx): """Connectivity List indexed by ID.""" @@ -99,21 +122,10 @@ def __init__(self, field: PropertyField, scoping: Scoping, mode: Mode): """Constructs a Connectivity list from a given PropertyField.""" super().__init__(field, scoping, mode) - def __getitem__(self, key: int) -> List[int]: + def __getitem__(self, id: int) -> List[int]: """Returns a list of indexes or IDs for a given ID, see ReturnMode Enum.""" - if self._mode == ReturnMode.IDS: - return self._get_ids_from_id(key) - elif self._mode == ReturnMode.IDX: - return self._get_idx_from_id(key) - raise ValueError(f"ReturnMode has an incorrect value") - - def _get_ids_from_id(self, id: int) -> List[int]: - """Helper method to retrieve list of IDs from a given ID.""" - return self._to_ids(self._get_idx_from_id(id)) - - def _get_idx_from_id(self, id: int) -> List[int]: - """Helper method to retrieve list of indexes from a given ID.""" - return self._field.get_entity_data_by_id(id) + idx = self._field.scoping.index(id) + return super().__getitem__(idx) def __contains__(self, l: List[int]) -> bool: """Not Implemented.""" @@ -126,3 +138,6 @@ def __iter__(self) -> ConnectivityListIterator: def __len__(self) -> int: """Returns the number of entities.""" return len(self._field._get_data_pointer()) + + def __repr__(self) -> str: + return f"ConnectivityListById({super().__str__()}, __len__={self.__len__()})" \ No newline at end of file diff --git a/src/ansys/dpf/post/elements.py b/src/ansys/dpf/post/elements.py index ef9830cc7..24079a1fd 100644 --- a/src/ansys/dpf/post/elements.py +++ b/src/ansys/dpf/post/elements.py @@ -10,35 +10,67 @@ import ansys.dpf.core.nodes as nodes # noqa: F401 -class ElementType(dpf.ElementDescriptor): +class ElementType: """Wrapper type to instantiate an ElementDescriptor from an int.""" def __init__(self, arg: Union[dpf.ElementDescriptor, int]): """Constructs an ElementType from an existing descriptor or enum id.""" - _obj = arg + + self._el_desc = arg if isinstance(arg, int): - _obj = dpf.element_types.descriptor(arg) + self._el_desc = dpf.element_types.descriptor(arg) - if not isinstance(_obj, dpf.ElementDescriptor): + if not isinstance(self._el_desc, dpf.ElementDescriptor): raise TypeError(f"Given argument is not an int nor an ElementDescriptor") - super().__init__( - _obj.enum_id, - _obj.description, - _obj.name, - _obj.shape, - _obj.n_corner_nodes, - _obj.n_mid_nodes, - _obj.n_nodes, - _obj.is_solid, - _obj.is_shell, - _obj.is_beam, - _obj.is_quadratic, - ) - - def __str__(self): + + @property + def enum_id(self) -> int: + return self._el_desc.enum_id + + @property + def description(self) -> str: + return self._el_desc.description + + @property + def name(self) -> str: + return self._el_desc.name + + @property + def shape(self) -> str: + return self._el_desc.shape + + @property + def num_corner_nodes(self) -> int: + return self._el_desc.n_corner_nodes + + @property + def num_mid_nodes(self) -> int: + return self._el_desc.n_mid_nodes + + @property + def num_nodes(self) -> int: + return self._el_desc.n_nodes + + @property + def is_solid(self) -> bool: + return self._el_desc.is_solid + + @property + def is_shell(self) -> bool: + return self._el_desc.is_shell + + @property + def is_beam(self) -> bool: + return self._el_desc.is_beam + + @property + def is_quadratic(self) -> bool: + return self._el_desc.is_quadratic + + def __str__(self) -> str: """Returns a string representation of the Element Type.""" - return self.name + return self._el_desc.__str__().replace("Element descriptor\n------------------", "Element Type\n------------") def __repr__(self): """Returns a string representation of the Element Type.""" @@ -53,7 +85,7 @@ def __init__(self, elements: elements.Elements, index: int): self._elements = elements self._index = index - def _resolve(self): + def _resolve(self) -> elements.Element: """Returns the original Element object in the original list.""" return self._elements[self._index] @@ -88,9 +120,9 @@ def type_info(self) -> ElementType: return ElementType(self._resolve().type.value) @property - def type(self) -> int: + def type(self) -> dpf.element_types: """Returns the ID of the Element Type.""" - return self._resolve().type.value + return self._resolve().type @property def shape(self) -> str: @@ -104,7 +136,7 @@ def connectivity(self) -> List[int]: def __repr__(self) -> str: """Returns string representation of an Element.""" - return self._resolve().__repr__() + return f"Element(type={self.type},index={self.index},id={self.id},shape={self.shape})" def __str__(self) -> str: """Returns string representation of an Element.""" @@ -114,7 +146,7 @@ def __str__(self) -> str: class ElementListIterator(Iterator): """Iterator class for the ElementList.""" - def __init__(self, el_list: ElementListIdx): + def __init__(self, el_list: elements.Elements): """Constructs an Iterator from an element list.""" self._el_list = el_list self._idx = 0 @@ -124,7 +156,7 @@ def __next__(self) -> Element: if self._idx >= self._el_list.__len__(): raise StopIteration - ret = self._el_list[self._idx] + ret = Element(self._el_list, self._idx) self._idx += 1 return ret @@ -150,7 +182,7 @@ def __contains__(self, el: Element) -> bool: def __iter__(self) -> ElementListIterator: """Returns an Iterator object on the list.""" - return ElementListIterator(self) + return ElementListIterator(self._elements) def __len__(self) -> int: """Returns the number of elements in the list.""" @@ -160,6 +192,24 @@ def __len__(self) -> int: def by_id(self) -> ElementListById: """Returns an equivalent list accessible with ID instead of index.""" return ElementListById(self._elements) + + def _short_list(self) -> str: + _str = "[" + if self.__len__() > 3: + _fst = Element(self._elements, 0).type_info.name + _lst = Element(self._elements, self.__len__()-1).type_info.name + _str += f"{_fst}, ..., {_lst}" + else: + el_list = [Element(self._elements, idx) for idx in range(self.__len__())] + _str += ", ".join(map(lambda el: el.type_info.name, el_list)) + _str += "]" + return _str + + def __str__(self) -> str: + return self._short_list() + + def __repr__(self) -> str: + return f"ElementListIdx({self.__str__()}, __len__={self.__len__()})" class ElementListById(ElementListIdx): @@ -185,3 +235,6 @@ def __iter__(self) -> ElementListIterator: def __len__(self) -> int: """Returns the number of elements in the list.""" return self._elements.n_elements + + def __repr__(self): + return f"ElementListById({super().__str__()}, __len__={self.__len__()})" \ No newline at end of file diff --git a/src/ansys/dpf/post/nodes.py b/src/ansys/dpf/post/nodes.py index e8ad8e639..9ccceabf0 100644 --- a/src/ansys/dpf/post/nodes.py +++ b/src/ansys/dpf/post/nodes.py @@ -2,15 +2,47 @@ from __future__ import annotations +from typing import List + from collections.abc import Collection, Iterator import ansys.dpf.core.nodes as nodes +class Node: + def __init__(self, nodes: nodes.Nodes, index: int): + self._nodes = nodes + self._index = index + + def _resolve(self) -> nodes.Node: + return self._nodes[self._index] + + @property + def index(self) -> int: + return self._resolve().index + + @property + def id(self) -> int: + return self._resolve().id + + @property + def coordinates(self) -> List[float]: + return self._resolve().coordinates + + @property + def nodal_connectivity(self) -> List[int]: + return self._resolve().nodal_connectivity + + def __str__(self) -> str: + return f"Node(id={self.id}, coordinates={self.coordinates})" + + def __repr__(self) -> str: + return f"Node(id={self.id})" + class NodeListIterator(Iterator): """Iterator object for NodeListIdx.""" - def __init__(self, nodes: NodeListIdx): + def __init__(self, nodes: nodes.Nodes): """Constructs an iterator from a Node List.""" self._nodes = nodes self._idx = 0 @@ -20,7 +52,7 @@ def __next__(self) -> nodes.Node: if self._idx >= self._nodes.__len__(): raise StopIteration - ret = self._nodes[self._idx] + ret = Node(self._nodes, self._idx) self._idx += 1 return ret @@ -38,7 +70,7 @@ def __init__(self, nodes: nodes.Nodes): def __getitem__(self, idx: int) -> nodes.Node: """Returns a Node at a given index.""" - return self._nodes.node_by_index(idx) + return Node(self._nodes, idx) def __contains__(self, node: nodes.Node) -> bool: """Checks if given node is in the list.""" @@ -46,7 +78,7 @@ def __contains__(self, node: nodes.Node) -> bool: def __iter__(self) -> NodeListIterator: """Returns an iterator object on the list.""" - return NodeListIterator(self) + return NodeListIterator(self._nodes) def __len__(self) -> int: """Returns the number of nodes in the list.""" @@ -56,6 +88,24 @@ def __len__(self) -> int: def by_id(self) -> NodeListById: """Returns an equivalent list Accessible by ID.""" return NodeListById(self._nodes) + + def _short_list(self) -> str: + _str = "[" + if self.__len__() > 3: + _fst = Node(self._nodes, 0) + _lst = Node(self._nodes, self.__len__()-1) + _str += f"{_fst}, ..., {_lst}" + else: + el_list = [Node(self._nodes, idx) for idx in range(self.__len__())] + _str += ", ".join(map(lambda el: repr(el), el_list)) + _str += "]" + return _str + + def __str__(self) -> str: + return self._short_list() + + def __repr__(self) -> str: + return f"NodeListIdx({self.__str__()}, __len__={self.__len__()})" class NodeListById(NodeListIdx): @@ -67,7 +117,8 @@ def __init__(self, nodes: nodes.Nodes): def __getitem__(self, id: int) -> nodes.Node: """Returns a Node for a given ID.""" - return self._nodes.node_by_id(id) + idx = self._nodes.scoping.index(id) + return super().__getitem__(idx) def __contains__(self, node: nodes.Node) -> bool: """Checks if the given node is in the list.""" @@ -80,3 +131,6 @@ def __iter__(self) -> NodeListIterator: def __len__(self) -> int: """Returns the number of nodes in the list.""" return self._nodes.n_nodes + + def __repr__(self) -> str: + return f"NodeListById({super().__str__()}, __len__={self.__len__()})" \ No newline at end of file From 30f8186fd9e3cafed579d7d55f4c3bcbd7d91a2c Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Wed, 26 Apr 2023 17:27:03 +0200 Subject: [PATCH 51/99] Fixed string rep, added docstrings --- src/ansys/dpf/post/connectivity.py | 19 +++++++----- src/ansys/dpf/post/elements.py | 44 ++++++++++++++++++---------- src/ansys/dpf/post/nodes.py | 47 +++++++++++++++++++----------- 3 files changed, 71 insertions(+), 39 deletions(-) diff --git a/src/ansys/dpf/post/connectivity.py b/src/ansys/dpf/post/connectivity.py index 508953a77..05ef4013c 100644 --- a/src/ansys/dpf/post/connectivity.py +++ b/src/ansys/dpf/post/connectivity.py @@ -90,30 +90,34 @@ def __iter__(self) -> ConnectivityListIterator: def __len__(self) -> int: """Returns the number of entities.""" return len(self._field._get_data_pointer()) - + def _short_list(self) -> str: _str = "[" if self.__len__() > 3: _fst = self._field.get_entity_data(0) - _lst = self._field.get_entity_data(self.__len__()-1) + _lst = self._field.get_entity_data(self.__len__() - 1) if self._mode == ReturnMode.IDS: _fst = self._to_ids(_fst) _lst = self._to_ids(_lst) _str += f"{_fst}, ..., {_lst}" else: - conn_list = [self._field.get_entity_data(idx) for idx in range(self.__len__())] + conn_list = [ + self._field.get_entity_data(idx) for idx in range(self.__len__()) + ] if self._mode == ReturnMode.IDS: conn_list = list(map(self._to_ids, conn_list)) _str += ", ".join(map(lambda l: str(l), conn_list)) _str += "]" return _str - + def __str__(self) -> str: + """Returns string representation of ConnectivityListIdx.""" return self._short_list() - + def __repr__(self) -> str: + """Returns string representation of ConnectivityListIdx.""" return f"ConnectivityListIdx({self.__str__()},__len__={self.__len__()})" - + class ConnectivityListById(ConnectivityListIdx): """Connectivity List indexed by ID.""" @@ -140,4 +144,5 @@ def __len__(self) -> int: return len(self._field._get_data_pointer()) def __repr__(self) -> str: - return f"ConnectivityListById({super().__str__()}, __len__={self.__len__()})" \ No newline at end of file + """String representation of ConnectivityListById.""" + return f"ConnectivityListById({super().__str__()}, __len__={self.__len__()})" diff --git a/src/ansys/dpf/post/elements.py b/src/ansys/dpf/post/elements.py index 24079a1fd..e544fd7c8 100644 --- a/src/ansys/dpf/post/elements.py +++ b/src/ansys/dpf/post/elements.py @@ -15,7 +15,6 @@ class ElementType: def __init__(self, arg: Union[dpf.ElementDescriptor, int]): """Constructs an ElementType from an existing descriptor or enum id.""" - self._el_desc = arg if isinstance(arg, int): self._el_desc = dpf.element_types.descriptor(arg) @@ -23,54 +22,66 @@ def __init__(self, arg: Union[dpf.ElementDescriptor, int]): if not isinstance(self._el_desc, dpf.ElementDescriptor): raise TypeError(f"Given argument is not an int nor an ElementDescriptor") - @property def enum_id(self) -> int: + """Id of the Element type in the element_types enum.""" return self._el_desc.enum_id @property def description(self) -> str: + """Specifies the element geometry and integration order.""" return self._el_desc.description - + @property def name(self) -> str: + """Short name of the element type.""" return self._el_desc.name - + @property def shape(self) -> str: + """Can be ``"solid"``,``"shell"`` or ``"beam"``.""" return self._el_desc.shape - + @property def num_corner_nodes(self) -> int: + """Returns the number of corner nodes.""" return self._el_desc.n_corner_nodes - + @property def num_mid_nodes(self) -> int: + """Returns the number of middle nodes.""" return self._el_desc.n_mid_nodes - + @property def num_nodes(self) -> int: + """Returns the total number of nodes.""" return self._el_desc.n_nodes - + @property def is_solid(self) -> bool: + """Whether the element is a solid.""" return self._el_desc.is_solid - + @property def is_shell(self) -> bool: + """Whether the element is a shell.""" return self._el_desc.is_shell - + @property def is_beam(self) -> bool: + """Whether the element is a beam.""" return self._el_desc.is_beam @property def is_quadratic(self) -> bool: + """Whether the element is quadratic.""" return self._el_desc.is_quadratic def __str__(self) -> str: """Returns a string representation of the Element Type.""" - return self._el_desc.__str__().replace("Element descriptor\n------------------", "Element Type\n------------") + return self._el_desc.__str__().replace( + "Element descriptor\n------------------", "Element Type\n------------" + ) def __repr__(self): """Returns a string representation of the Element Type.""" @@ -120,7 +131,7 @@ def type_info(self) -> ElementType: return ElementType(self._resolve().type.value) @property - def type(self) -> dpf.element_types: + def type(self) -> int: """Returns the ID of the Element Type.""" return self._resolve().type @@ -192,12 +203,12 @@ def __len__(self) -> int: def by_id(self) -> ElementListById: """Returns an equivalent list accessible with ID instead of index.""" return ElementListById(self._elements) - + def _short_list(self) -> str: _str = "[" if self.__len__() > 3: _fst = Element(self._elements, 0).type_info.name - _lst = Element(self._elements, self.__len__()-1).type_info.name + _lst = Element(self._elements, self.__len__() - 1).type_info.name _str += f"{_fst}, ..., {_lst}" else: el_list = [Element(self._elements, idx) for idx in range(self.__len__())] @@ -206,9 +217,11 @@ def _short_list(self) -> str: return _str def __str__(self) -> str: + """Returns a string representation of ElementListIdx.""" return self._short_list() def __repr__(self) -> str: + """Returns a string representation of ElementListIdx.""" return f"ElementListIdx({self.__str__()}, __len__={self.__len__()})" @@ -237,4 +250,5 @@ def __len__(self) -> int: return self._elements.n_elements def __repr__(self): - return f"ElementListById({super().__str__()}, __len__={self.__len__()})" \ No newline at end of file + """Returns a string representation of ElementListById.""" + return f"ElementListById({super().__str__()}, __len__={self.__len__()})" diff --git a/src/ansys/dpf/post/nodes.py b/src/ansys/dpf/post/nodes.py index 9ccceabf0..a17d39379 100644 --- a/src/ansys/dpf/post/nodes.py +++ b/src/ansys/dpf/post/nodes.py @@ -1,41 +1,51 @@ -"""This module contains NodeList class.""" +"""This module contains Node-related wrapper classes.""" from __future__ import annotations -from typing import List - from collections.abc import Collection, Iterator +from typing import List import ansys.dpf.core.nodes as nodes + class Node: + """Wrapper class around dpf.core.nodes.Node.""" + def __init__(self, nodes: nodes.Nodes, index: int): + """Constructs a Node from its index and the original list.""" self._nodes = nodes self._index = index - + def _resolve(self) -> nodes.Node: + """Returns the original Node object in the original list.""" return self._nodes[self._index] @property def index(self) -> int: + """Returns the index of the node (zero-based).""" return self._resolve().index - + @property def id(self) -> int: + """Returns the ID of the node.""" return self._resolve().id - + @property def coordinates(self) -> List[float]: + """Cartersian coordinates of the node.""" return self._resolve().coordinates - + @property def nodal_connectivity(self) -> List[int]: + """Elements indices connected to the node.""" return self._resolve().nodal_connectivity - + def __str__(self) -> str: + """Returns a string representation of the node.""" return f"Node(id={self.id}, coordinates={self.coordinates})" - + def __repr__(self) -> str: + """Returns a string representation of the node.""" return f"Node(id={self.id})" @@ -47,7 +57,7 @@ def __init__(self, nodes: nodes.Nodes): self._nodes = nodes self._idx = 0 - def __next__(self) -> nodes.Node: + def __next__(self) -> Node: """Returns the next Node in the list.""" if self._idx >= self._nodes.__len__(): raise StopIteration @@ -68,7 +78,7 @@ def __init__(self, nodes: nodes.Nodes): """Constructs a NodeList from an existing dpf.core.nodes.Nodes object.""" self._nodes = nodes - def __getitem__(self, idx: int) -> nodes.Node: + def __getitem__(self, idx: int) -> Node: """Returns a Node at a given index.""" return Node(self._nodes, idx) @@ -88,23 +98,25 @@ def __len__(self) -> int: def by_id(self) -> NodeListById: """Returns an equivalent list Accessible by ID.""" return NodeListById(self._nodes) - + def _short_list(self) -> str: _str = "[" if self.__len__() > 3: _fst = Node(self._nodes, 0) - _lst = Node(self._nodes, self.__len__()-1) + _lst = Node(self._nodes, self.__len__() - 1) _str += f"{_fst}, ..., {_lst}" else: el_list = [Node(self._nodes, idx) for idx in range(self.__len__())] _str += ", ".join(map(lambda el: repr(el), el_list)) _str += "]" return _str - + def __str__(self) -> str: + """Returns a string representation of NodeListIdx.""" return self._short_list() - + def __repr__(self) -> str: + """Returns a string representation of NodeListIdx.""" return f"NodeListIdx({self.__str__()}, __len__={self.__len__()})" @@ -115,7 +127,7 @@ def __init__(self, nodes: nodes.Nodes): """Constructs a list from an existing core.nodes.Nodes object.""" super().__init__(nodes) - def __getitem__(self, id: int) -> nodes.Node: + def __getitem__(self, id: int) -> Node: """Returns a Node for a given ID.""" idx = self._nodes.scoping.index(id) return super().__getitem__(idx) @@ -133,4 +145,5 @@ def __len__(self) -> int: return self._nodes.n_nodes def __repr__(self) -> str: - return f"NodeListById({super().__str__()}, __len__={self.__len__()})" \ No newline at end of file + """Returns a string representation of NodeListById.""" + return f"NodeListById({super().__str__()}, __len__={self.__len__()})" From 4a3821751aa4726feecae0b7b0a91a4413c5545a Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Wed, 26 Apr 2023 18:06:40 +0200 Subject: [PATCH 52/99] Fixed server variable in mock class PropertyFieldFC --- src/ansys/dpf/post/fields_container.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ansys/dpf/post/fields_container.py b/src/ansys/dpf/post/fields_container.py index 06676cc45..d608a4698 100644 --- a/src/ansys/dpf/post/fields_container.py +++ b/src/ansys/dpf/post/fields_container.py @@ -42,7 +42,7 @@ def __init__(self, fields_container=None, server=None): # default constructor self._labels = [] # used by Dataframe self.scopings = [] - self.server = None # used by Dataframe + self._server = None # used by Dataframe self.label_spaces = [] self.ids = [] @@ -51,13 +51,13 @@ def __init__(self, fields_container=None, server=None): if fields_container is not None: self._labels = copy.deepcopy(fields_container.labels) self.scopings = copy.deepcopy(fields_container.scopings) - self.server = copy.deepcopy(fields_container.server) + self._server = copy.deepcopy(fields_container.server) self.label_spaces = copy.deepcopy(fields_container.label_spaces) self.ids = copy.deepcopy(fields_container.ids) # server copy if server is not None: - self.server = server + self._server = server # Collection def __str__(self): @@ -113,7 +113,7 @@ def add_entry(self, label_space: Dict[str, int], value): new_id = self._new_id() if hasattr(value, "_server"): - self.server = value._server + self._server = value._server # add Label Space self.label_spaces.append(LabelSpaceKV(label_space, value)) @@ -173,7 +173,7 @@ def _new_id(self): # FieldsContainer def create_subtype(self, obj_by_copy): """Instantiate a PropertyField with given instance, using the server of the container.""" - return PropertyField(property_field=obj_by_copy, server=self.server) + return PropertyField(property_field=obj_by_copy, server=self._server) def get_fields_by_time_complex_ids(self, timeid=None, complexid=None): """Returns fields at a requested time or complex ID.""" From a1fd26c169583f783fdfefe12517aa5eb146027a Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Thu, 27 Apr 2023 12:10:02 +0200 Subject: [PATCH 53/99] Added minimal functionality for df.select on PropertyFields --- src/ansys/dpf/post/dataframe.py | 23 +++++++++------ src/ansys/dpf/post/fields_container.py | 39 +++++++++++++++++++++----- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/ansys/dpf/post/dataframe.py b/src/ansys/dpf/post/dataframe.py index 907190a84..6c92979e1 100644 --- a/src/ansys/dpf/post/dataframe.py +++ b/src/ansys/dpf/post/dataframe.py @@ -284,13 +284,16 @@ def select(self, **kwargs) -> DataFrame: f"Selection on a DataFrame with index " f"'{mesh_index_name}' is not yet supported" ) - rescope_fc = dpf.operators.scoping.rescope_fc( - fields_container=input_fc, - mesh_scoping=mesh_scoping, - server=server, - ) - out = rescope_fc.outputs.fields_container - mesh_index = MeshIndex(location=location, values=mesh_scoping.ids) + if isinstance(input_fc, PropertyFieldsContainer): + fc = input_fc.rescope(mesh_scoping) + else: + rescope_fc = dpf.operators.scoping.rescope_fc( + fields_container=input_fc, + mesh_scoping=mesh_scoping, + server=server, + ) + out = rescope_fc.outputs.fields_container + mesh_index = MeshIndex(location=location, values=mesh_scoping.ids) elif ( mesh_index_name in axis_kwargs.keys() and mesh_index.location == locations.elemental_nodal @@ -300,7 +303,6 @@ def select(self, **kwargs) -> DataFrame: "is not yet supported" ) - if out is not None: wf.set_output_name("out", out) fc = wf.get_output("out", dpf.FieldsContainer) @@ -320,6 +322,9 @@ def select(self, **kwargs) -> DataFrame: results_index, set_index, ] + if isinstance(fc, PropertyFieldsContainer): + column_indexes = [results_index] + label_indexes = [] for label in fc.labels: if label not in ["time"]: @@ -618,6 +623,8 @@ def treat_elemental_nodal(treat_lines, pos, n_comp, n_ent, n_lines): for value in values[: len(lines) - (num_column_indexes + 1)]: if value is not None: value_string = f"{value:.{max_colwidth - 8}e}".rjust(max_colwidth) + if np.issubdtype(type(value), np.integer): + value_string = f"{value}".rjust(max_colwidth) else: value_string = empty value_strings.append(value_string) diff --git a/src/ansys/dpf/post/fields_container.py b/src/ansys/dpf/post/fields_container.py index d608a4698..e78df1d16 100644 --- a/src/ansys/dpf/post/fields_container.py +++ b/src/ansys/dpf/post/fields_container.py @@ -8,6 +8,7 @@ import ansys.dpf.core as dpf from ansys.dpf.core.property_field import PropertyField +import numpy as np from ansys import dpf @@ -20,14 +21,20 @@ def __init__(self, _dict: Dict[str, int], _field): self._dict = _dict self._field = _field + @property def dict(self): """Returns the associated dictionary.""" return self._dict + @property def field(self): """Returns the associated field.""" return self._field + @field.setter + def field(self, value): + self._field = value + def __str__(self): """Returns a string representationf of the association.""" field_str = str(self._field).replace("\n", " ") @@ -50,11 +57,14 @@ def __init__(self, fields_container=None, server=None): # PropertyFieldsContainer copy if fields_container is not None: self._labels = copy.deepcopy(fields_container.labels) - self.scopings = copy.deepcopy(fields_container.scopings) - self._server = copy.deepcopy(fields_container.server) + # self.scopings = copy.deepcopy(fields_container.scopings) + self._server = copy.deepcopy(fields_container._server) + + # self.ids = copy.deepcopy(fields_container.ids) + + for ls in fields_container.label_spaces: + self.add_entry(copy.deepcopy(ls.dict), ls.field.as_local_field()) - self.label_spaces = copy.deepcopy(fields_container.label_spaces) - self.ids = copy.deepcopy(fields_container.ids) # server copy if server is not None: self._server = server @@ -130,7 +140,7 @@ def get_entries(self, label_space_or_index): """Returns a list of fields from a complete or partial specification of a dictionary.""" if isinstance(label_space_or_index, int): idx: int = label_space_or_index - return [self.label_spaces[idx].field()] + return [self.label_spaces[idx].field] else: _dict: Dict[str, int] = label_space_or_index are_keys_in_labels = [key in self.labels for key in _dict.keys()] @@ -141,11 +151,11 @@ def get_entries(self, label_space_or_index): to_remove = set() for idx in remaining: ls = self.label_spaces[idx] - if ls.dict()[key] != val: + if ls.dict[key] != val: to_remove.add(idx) remaining = remaining.difference(to_remove) - idx_to_field = lambda idx: self.label_spaces[idx].field() + idx_to_field = lambda idx: self.label_spaces[idx].field return list(map(idx_to_field, remaining)) else: bad_idx = are_keys_in_labels.index(False) @@ -287,3 +297,18 @@ def __pow__(self, value): def __mul__(self, value): """Not implemented.""" raise NotImplementedError + + def _set_field(self, ls_idx, field): + self.label_spaces[ls_idx].field = field + + def rescope(self, scoping: dpf.Scoping): + """Helper function to reproduce functionality of rescope_fc Operator.""" + copy_fc = PropertyFieldsContainer(self, server=None) + for idx, label_space in enumerate(copy_fc.label_spaces): + pfield = PropertyField(location=label_space.field.location) + pfield.data = np.ravel( + [label_space._field.get_entity_data_by_id(id) for id in scoping.ids] + ) + pfield.scoping.ids = scoping.ids + copy_fc._set_field(idx, pfield) + return copy_fc From 97e13fee8e1af2dab57e9f899ab030503694117f Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Thu, 27 Apr 2023 12:15:21 +0200 Subject: [PATCH 54/99] fixed mistake --- src/ansys/dpf/post/dataframe.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ansys/dpf/post/dataframe.py b/src/ansys/dpf/post/dataframe.py index 6c92979e1..2c83de7ea 100644 --- a/src/ansys/dpf/post/dataframe.py +++ b/src/ansys/dpf/post/dataframe.py @@ -303,6 +303,7 @@ def select(self, **kwargs) -> DataFrame: "is not yet supported" ) + if out is not None: wf.set_output_name("out", out) fc = wf.get_output("out", dpf.FieldsContainer) From a9316a32f93056f898e3d06df9a77c8f11cf94a8 Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Tue, 2 May 2023 09:34:19 +0200 Subject: [PATCH 55/99] Added skin getter method --- src/ansys/dpf/post/mesh.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index e36e9d0be..df8ddb2f0 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -9,6 +9,7 @@ from typing import List import ansys.dpf.core as dpf +from ansys.dpf.core.common import types from ansys.dpf.core.nodes import Node import ansys.dpf.post as post @@ -18,6 +19,7 @@ from ansys.dpf.post.fields_container import PropertyFieldsContainer from ansys.dpf.post.named_selection import NamedSelectionsDict from ansys.dpf.post.nodes import NodeListIdx +from ansys.dpf.post.selection import SpatialSelection, _WfNames class Mesh: @@ -155,6 +157,19 @@ def unit(self) -> str: def unit(self, value: str): self._meshed_region.unit = value + def skin(self) -> Mesh: + """Returns the skin mesh of the current mesh.""" + selection = SpatialSelection(server=self._meshed_region._server) + selection.select_skin() + selection._selection.progress_bar = False + if selection.requires_mesh: + selection._selection.connect(_WfNames.initial_mesh, self._core_object) + + meshed_region = selection._selection.get_output( + _WfNames.skin, types.meshed_region + ) + return Mesh(meshed_region) + @property def _core_object(self): """Returns the underlying PyDPF-Core class:`ansys.dpf.core.MeshedRegion` object.""" From 19a445219ce2ec86ef7e753b7b34ee9d80c38caa Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Tue, 2 May 2023 09:42:49 +0200 Subject: [PATCH 56/99] Renamed connectivity getters, updated tests --- src/ansys/dpf/post/elements.py | 2 +- src/ansys/dpf/post/nodes.py | 2 +- tests/test_mesh.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ansys/dpf/post/elements.py b/src/ansys/dpf/post/elements.py index e544fd7c8..40350c2f4 100644 --- a/src/ansys/dpf/post/elements.py +++ b/src/ansys/dpf/post/elements.py @@ -141,7 +141,7 @@ def shape(self) -> str: return self._resolve().shape @property - def connectivity(self) -> List[int]: + def to_node_connectivity(self) -> List[int]: """See :py:meth:`ansys.dpf.core.elements.Element.connectivity`.""" return self._resolve().connectivity diff --git a/src/ansys/dpf/post/nodes.py b/src/ansys/dpf/post/nodes.py index a17d39379..268d641e1 100644 --- a/src/ansys/dpf/post/nodes.py +++ b/src/ansys/dpf/post/nodes.py @@ -36,7 +36,7 @@ def coordinates(self) -> List[float]: return self._resolve().coordinates @property - def nodal_connectivity(self) -> List[int]: + def to_element_connectivity(self) -> List[int]: """Elements indices connected to the node.""" return self._resolve().nodal_connectivity diff --git a/tests/test_mesh.py b/tests/test_mesh.py index 5337a890d..a0121b640 100644 --- a/tests/test_mesh.py +++ b/tests/test_mesh.py @@ -66,7 +66,7 @@ def test_mesh_elements(mesh): assert elem_idx_0.index == 0 assert elem_id_8.id == 8 assert elem_idx_0.num_nodes == 20 - assert elem_idx_0.type == dpf.element_types.Hex20.value + assert elem_idx_0.type == dpf.element_types.Hex20 assert elem_idx_0.shape == "solid" assert mesh.get_element_by_id(elem_idx_0.id).index == 0 @@ -76,7 +76,7 @@ def test_mesh_nodal_connectivity(mesh): conn_node_get_id = mesh.node_to_element_ids_connectivity node_idx_0 = mesh.nodes[0] - conn_idx = node_idx_0.nodal_connectivity + conn_idx = node_idx_0.to_element_connectivity conn_id = [mesh.elements[idx].id for idx in conn_idx] assert len(conn_idx) == len(conn_id) @@ -89,7 +89,7 @@ def test_mesh_elemental_connectivity(mesh): conn_elem_get_id = mesh.element_to_node_ids_connectivity elem_idx_0 = mesh.elements[0] - conn_idx = elem_idx_0.connectivity + conn_idx = elem_idx_0.to_node_connectivity conn_id = [mesh.nodes[idx].id for idx in conn_idx] assert len(conn_idx) == len(conn_id) From 354cd08338a46c9db9e30c6ff0ff163e085cac7d Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Mon, 15 May 2023 10:34:25 +0200 Subject: [PATCH 57/99] Revert "Added skin getter method" This reverts commit a9316a32f93056f898e3d06df9a77c8f11cf94a8. --- src/ansys/dpf/post/mesh.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index df8ddb2f0..e36e9d0be 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -9,7 +9,6 @@ from typing import List import ansys.dpf.core as dpf -from ansys.dpf.core.common import types from ansys.dpf.core.nodes import Node import ansys.dpf.post as post @@ -19,7 +18,6 @@ from ansys.dpf.post.fields_container import PropertyFieldsContainer from ansys.dpf.post.named_selection import NamedSelectionsDict from ansys.dpf.post.nodes import NodeListIdx -from ansys.dpf.post.selection import SpatialSelection, _WfNames class Mesh: @@ -157,19 +155,6 @@ def unit(self) -> str: def unit(self, value: str): self._meshed_region.unit = value - def skin(self) -> Mesh: - """Returns the skin mesh of the current mesh.""" - selection = SpatialSelection(server=self._meshed_region._server) - selection.select_skin() - selection._selection.progress_bar = False - if selection.requires_mesh: - selection._selection.connect(_WfNames.initial_mesh, self._core_object) - - meshed_region = selection._selection.get_output( - _WfNames.skin, types.meshed_region - ) - return Mesh(meshed_region) - @property def _core_object(self): """Returns the underlying PyDPF-Core class:`ansys.dpf.core.MeshedRegion` object.""" From 76507ba83881d1175e1c4c0e65579a93fdb64a63 Mon Sep 17 00:00:00 2001 From: Antoine Karcher Date: Mon, 15 May 2023 15:53:47 +0200 Subject: [PATCH 58/99] Make PropertyFieldsContainer private --- src/ansys/dpf/post/dataframe.py | 10 +++++----- src/ansys/dpf/post/mesh.py | 6 +++--- .../{fields_container.py => prop_fields_container.py} | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) rename src/ansys/dpf/post/{fields_container.py => prop_fields_container.py} (98%) diff --git a/src/ansys/dpf/post/dataframe.py b/src/ansys/dpf/post/dataframe.py index 28f0a9a9e..a78278568 100644 --- a/src/ansys/dpf/post/dataframe.py +++ b/src/ansys/dpf/post/dataframe.py @@ -17,7 +17,7 @@ import numpy as np from ansys.dpf.post import locations, shell_layers -from ansys.dpf.post.fields_container import PropertyFieldsContainer +from ansys.dpf.post.prop_fields_container import _PropertyFieldsContainer from ansys.dpf.post.index import ( CompIndex, Index, @@ -39,7 +39,7 @@ class DataFrame: def __init__( self, - data: Union[dpf.FieldsContainer, PropertyFieldsContainer], + data: Union[dpf.FieldsContainer, _PropertyFieldsContainer], index: Union[MultiIndex, Index, List[int]], columns: Union[MultiIndex, Index, List[str], None] = None, ): @@ -56,7 +56,7 @@ def __init__( """ self._index = index if isinstance(data, dpf.FieldsContainer) or isinstance( - data, PropertyFieldsContainer + data, _PropertyFieldsContainer ): self._fc = data # if index is None: @@ -286,7 +286,7 @@ def select(self, **kwargs) -> DataFrame: f"Selection on a DataFrame with index " f"'{mesh_index_name}' is not yet supported" ) - if isinstance(input_fc, PropertyFieldsContainer): + if isinstance(input_fc, _PropertyFieldsContainer): fc = input_fc.rescope(mesh_scoping) else: rescope_fc = dpf.operators.scoping.rescope_fc( @@ -325,7 +325,7 @@ def select(self, **kwargs) -> DataFrame: results_index, set_index, ] - if isinstance(fc, PropertyFieldsContainer): + if isinstance(fc, _PropertyFieldsContainer): column_indexes = [results_index] label_indexes = [] diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index e36e9d0be..774ef03e6 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -15,7 +15,7 @@ from ansys.dpf.post import index, locations from ansys.dpf.post.connectivity import ConnectivityListIdx, ReturnMode from ansys.dpf.post.elements import Element, ElementListIdx -from ansys.dpf.post.fields_container import PropertyFieldsContainer +from ansys.dpf.post.prop_fields_container import _PropertyFieldsContainer from ansys.dpf.post.named_selection import NamedSelectionsDict from ansys.dpf.post.nodes import NodeListIdx @@ -78,7 +78,7 @@ def get_node_by_id(self, id: int) -> Node: def element_types(self) -> post.DataFrame: """Returns a DataFrame containing element types ID.""" label = "elem_type_id" - fields_container = PropertyFieldsContainer() + fields_container = _PropertyFieldsContainer() field = self._meshed_region.elements.element_types_field fields_container.add_field(label_space={}, field=field) @@ -100,7 +100,7 @@ def element_types(self) -> post.DataFrame: def materials(self) -> post.DataFrame: """Returns a DataFrame containing element materials ID.""" label = "material_id" - fields_container = PropertyFieldsContainer() + fields_container = _PropertyFieldsContainer() field = self._meshed_region.elements.materials_field fields_container.add_field(label_space={}, field=field) diff --git a/src/ansys/dpf/post/fields_container.py b/src/ansys/dpf/post/prop_fields_container.py similarity index 98% rename from src/ansys/dpf/post/fields_container.py rename to src/ansys/dpf/post/prop_fields_container.py index e78df1d16..c10cd30bb 100644 --- a/src/ansys/dpf/post/fields_container.py +++ b/src/ansys/dpf/post/prop_fields_container.py @@ -13,7 +13,7 @@ from ansys import dpf -class LabelSpaceKV: +class _LabelSpaceKV: """Class for internal use to associate a label space with a field.""" def __init__(self, _dict: Dict[str, int], _field): @@ -41,7 +41,7 @@ def __str__(self): return f"Label Space: {self._dict} with field {field_str}" -class PropertyFieldsContainer(Sequence): +class _PropertyFieldsContainer(Sequence): """Minimal implementation of a FieldsContainer specialized for PropertyFieldsContainer.""" def __init__(self, fields_container=None, server=None): @@ -126,7 +126,7 @@ def add_entry(self, label_space: Dict[str, int], value): self._server = value._server # add Label Space - self.label_spaces.append(LabelSpaceKV(label_space, value)) + self.label_spaces.append(_LabelSpaceKV(label_space, value)) # Update IDs self.ids.append(new_id) @@ -303,7 +303,7 @@ def _set_field(self, ls_idx, field): def rescope(self, scoping: dpf.Scoping): """Helper function to reproduce functionality of rescope_fc Operator.""" - copy_fc = PropertyFieldsContainer(self, server=None) + copy_fc = _PropertyFieldsContainer(self, server=None) for idx, label_space in enumerate(copy_fc.label_spaces): pfield = PropertyField(location=label_space.field.location) pfield.data = np.ravel( From d5e06304415985291916e5909bdd74727099f86f Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Fri, 7 Jul 2023 17:39:41 +0200 Subject: [PATCH 59/99] Fix styling --- src/ansys/dpf/post/mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index 774ef03e6..8ce1c020b 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -15,9 +15,9 @@ from ansys.dpf.post import index, locations from ansys.dpf.post.connectivity import ConnectivityListIdx, ReturnMode from ansys.dpf.post.elements import Element, ElementListIdx -from ansys.dpf.post.prop_fields_container import _PropertyFieldsContainer from ansys.dpf.post.named_selection import NamedSelectionsDict from ansys.dpf.post.nodes import NodeListIdx +from ansys.dpf.post.prop_fields_container import _PropertyFieldsContainer class Mesh: From 474fbf3cde1c8af2770e58b4b6a983c497bc72f3 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Fri, 7 Jul 2023 17:50:04 +0200 Subject: [PATCH 60/99] Update test ref --- tests/test_mesh.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test_mesh.py b/tests/test_mesh.py index a0121b640..13de94964 100644 --- a/tests/test_mesh.py +++ b/tests/test_mesh.py @@ -109,14 +109,14 @@ def test_mesh_coordinates(mesh): coord = mesh.coordinates print(coord) ref = """ - results coord (m) - node_ids components - 1 X 1.5000e-02 - Y 4.5000e-02 - Z 1.5000e-02 - 2 X 1.5000e-02 - Y 4.5000e-02 - Z 0.0000e+00 - ... + results coord (m) + node_ids components + 1 X 1.5000e-02 + Y 4.5000e-02 + Z 1.5000e-02 + 2 X 1.5000e-02 + Y 4.5000e-02 + Z 0.0000e+00 + ... ... ... """ # noqa assert str(coord) == ref From c0e9e04271f230982485341a046ab5cda6ffc3d4 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Fri, 7 Jul 2023 17:51:55 +0200 Subject: [PATCH 61/99] Remove warning --- tests/test_resultdata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_resultdata.py b/tests/test_resultdata.py index f94883768..228195934 100755 --- a/tests/test_resultdata.py +++ b/tests/test_resultdata.py @@ -330,7 +330,7 @@ def test_plot_on_coordinates(model_ns): solution = post.load_solution(model_ns) path = post.create_path_on_coordinates(coordinates=coordinates) displacement = solution.displacement(path=path) - displacement.vector.plot_contour(notebook=False) + displacement.vector.plot_contour() @pytest.mark.skipif( From 026cee4dd16a7e3489e4bd1a1566e48e5a37f524 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Fri, 7 Jul 2023 20:03:47 +0200 Subject: [PATCH 62/99] Fix code quality --- src/ansys/dpf/post/common.py | 2 +- src/ansys/dpf/post/connectivity.py | 33 ++++++++++++++++----- src/ansys/dpf/post/elements.py | 24 +++++++-------- src/ansys/dpf/post/mesh.py | 4 +-- src/ansys/dpf/post/meshes.py | 4 +-- src/ansys/dpf/post/named_selection.py | 4 +-- src/ansys/dpf/post/nodes.py | 4 +-- src/ansys/dpf/post/prop_fields_container.py | 8 ++--- tests/test_meshes.py | 2 +- 9 files changed, 51 insertions(+), 34 deletions(-) diff --git a/src/ansys/dpf/post/common.py b/src/ansys/dpf/post/common.py index 59ca7ddb8..d62974af0 100644 --- a/src/ansys/dpf/post/common.py +++ b/src/ansys/dpf/post/common.py @@ -4,7 +4,7 @@ ------ """ -from ansys.dpf.core.common import elemental_properties # noqa: F401 +from ansys.dpf.core.common import elemental_properties # noqa: F401, W0611 from ansys.dpf.post.harmonic_mechanical_simulation import HarmonicMechanicalSimulation from ansys.dpf.post.modal_mechanical_simulation import ModalMechanicalSimulation diff --git a/src/ansys/dpf/post/connectivity.py b/src/ansys/dpf/post/connectivity.py index 05ef4013c..9ff4c2810 100644 --- a/src/ansys/dpf/post/connectivity.py +++ b/src/ansys/dpf/post/connectivity.py @@ -6,6 +6,7 @@ from enum import Enum from typing import List +from ansys.dpf.core.any import Any from ansys.dpf.core.property_field import PropertyField from ansys.dpf.core.scoping import Scoping @@ -36,27 +37,45 @@ def __next__(self) -> Any: def __iter__(self) -> ConnectivityListIterator: """Returns a new ConnectivityListIterator.""" - return ConnectivityListIdx(self._conn_list) + return ConnectivityListIdx(field=self._conn_list) class ConnectivityListIdx(Collection): """Very basic wrapper around elemental and nodal connectivities fields.""" - def __init__(self, field: PropertyField, scoping: Scoping, mode: ReturnMode): + def __init__( + self, + field: PropertyField, + scoping: Union[Scoping, None] = None, + mode: Union[ReturnMode, None] = None, + ): """Constructs a ConnectivityList by wrapping given PropertyField.""" self._field = field self._mode = mode self._scoping = scoping - + self._idx = 0 self.local_scoping = None + def __next__(self) -> Any: + """Returns the next element in the list.""" + if self._idx >= len(self): + raise StopIteration + if self._mode == ReturnMode.IDS: + out = self._get_ids_from_idx(self._idx) + elif self._mode == ReturnMode.IDX: + out = self._get_idx_from_idx(self._idx) + else: + raise ValueError(f"ReturnMode has an incorrect value") + self._idx += 1 + return out + def __getitem__(self, idx: int) -> List[int]: """Returns a list of indexes or IDs for a given index, see ReturnMode Enum.""" if self._mode == ReturnMode.IDS: return self._get_ids_from_idx(idx) elif self._mode == ReturnMode.IDX: return self._get_idx_from_idx(idx) - raise ValueError(f"ReturnMode has an incorrect value") + raise ValueError("ReturnMode has an incorrect value") def _get_ids_from_idx(self, idx: int) -> List[int]: """Helper method to retrieve list of IDs from a given index.""" @@ -106,7 +125,7 @@ def _short_list(self) -> str: ] if self._mode == ReturnMode.IDS: conn_list = list(map(self._to_ids, conn_list)) - _str += ", ".join(map(lambda l: str(l), conn_list)) + _str += ", ".join(map(str, conn_list)) _str += "]" return _str @@ -122,11 +141,11 @@ def __repr__(self) -> str: class ConnectivityListById(ConnectivityListIdx): """Connectivity List indexed by ID.""" - def __init__(self, field: PropertyField, scoping: Scoping, mode: Mode): + def __init__(self, field: PropertyField, scoping: Scoping, mode: ReturnMode): """Constructs a Connectivity list from a given PropertyField.""" super().__init__(field, scoping, mode) - def __getitem__(self, id: int) -> List[int]: + def __getitem__(self, id: int) -> List[int]: # noqa: W0622 """Returns a list of indexes or IDs for a given ID, see ReturnMode Enum.""" idx = self._field.scoping.index(id) return super().__getitem__(idx) diff --git a/src/ansys/dpf/post/elements.py b/src/ansys/dpf/post/elements.py index 40350c2f4..bfd3a967d 100644 --- a/src/ansys/dpf/post/elements.py +++ b/src/ansys/dpf/post/elements.py @@ -6,8 +6,8 @@ from typing import List, Union import ansys.dpf.core as dpf -import ansys.dpf.core.elements as elements -import ansys.dpf.core.nodes as nodes # noqa: F401 +from ansys.dpf.core.elements import Element, Elements +from ansys.dpf.core.nodes import Node class ElementType: @@ -20,7 +20,7 @@ def __init__(self, arg: Union[dpf.ElementDescriptor, int]): self._el_desc = dpf.element_types.descriptor(arg) if not isinstance(self._el_desc, dpf.ElementDescriptor): - raise TypeError(f"Given argument is not an int nor an ElementDescriptor") + raise TypeError("Given argument is not an int nor an ElementDescriptor") @property def enum_id(self) -> int: @@ -91,12 +91,12 @@ def __repr__(self): class Element: """Proxy class wrapping dpf.core.elements.Element.""" - def __init__(self, elements: elements.Elements, index: int): + def __init__(self, elements: Elements, index: int): """Constructs a Proxy Element object.""" self._elements = elements self._index = index - def _resolve(self) -> elements.Element: + def _resolve(self) -> Element: """Returns the original Element object in the original list.""" return self._elements[self._index] @@ -116,7 +116,7 @@ def index(self) -> int: return self._resolve().index @property - def nodes(self) -> List[nodes.Node]: + def nodes(self) -> List[Node]: """See :py:meth:`ansys.dpf.core.elements.Element.nodes`.""" return self._resolve().nodes @@ -157,7 +157,7 @@ def __str__(self) -> str: class ElementListIterator(Iterator): """Iterator class for the ElementList.""" - def __init__(self, el_list: elements.Elements): + def __init__(self, el_list: Elements): """Constructs an Iterator from an element list.""" self._el_list = el_list self._idx = 0 @@ -179,13 +179,13 @@ def __iter__(self) -> Iterator: class ElementListIdx(Collection): """List of Elements.""" - def __init__(self, elements: elements.Elements): + def __init__(self, elements: Elements): """Constructs list from existing dpf.core.elements.Elements list.""" self._elements = elements - def __getitem__(self, idx: int) -> Element: + def __getitem__(self, index: int) -> Element: """Delegates to element_by_id() if by_id, otherwise to element_by_index().""" - return Element(self._elements, idx) + return Element(self._elements, index) def __contains__(self, el: Element) -> bool: """Checks if the given element in the list.""" @@ -228,11 +228,11 @@ def __repr__(self) -> str: class ElementListById(ElementListIdx): """Wrapper class for accessing Elements by ID instead of index.""" - def __init__(self, elements: elements.Elements): + def __init__(self, elements: Elements): """Constructs an ElementListById from an Elements instance.""" super().__init__(elements) - def __getitem__(self, id: int) -> Element: + def __getitem__(self, id: int) -> Element: # noqa: W0622 """Access an Element with an ID.""" idx = self._elements.scoping.index(id) return super().__getitem__(idx) diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index 8ce1c020b..5339078b6 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -61,7 +61,7 @@ def elements(self) -> ElementListIdx: """Returns a list of elements indexed by ID.""" return ElementListIdx(self._meshed_region.elements) - def get_element_by_id(self, id: int) -> Element: + def get_element_by_id(self, id: int) -> Element: # noqa: W0622 """Returns an element in the mesh from a given ID.""" return self.elements.by_id[id] @@ -70,7 +70,7 @@ def nodes(self) -> NodeListIdx: """Returns a list of nodes indexed by ID.""" return NodeListIdx(self._meshed_region.nodes) - def get_node_by_id(self, id: int) -> Node: + def get_node_by_id(self, id: int) -> Node: # noqa: W0622 """Returns a node of the mesh from a given ID.""" return self.nodes.by_id[id] diff --git a/src/ansys/dpf/post/meshes.py b/src/ansys/dpf/post/meshes.py index ca7dc802f..c4a77dddf 100644 --- a/src/ansys/dpf/post/meshes.py +++ b/src/ansys/dpf/post/meshes.py @@ -91,14 +91,14 @@ def select(self, **kwargs) -> Union[Mesh, Meshes, None]: initial_labels = self._core_object.labels # Filter labels label_space = {} - for key in kwargs.keys(): + for key in kwargs: if key in initial_labels: label_space[key] = kwargs[key] # selected_meshes = self._core_object.get_meshes(label_space=label_space) # Create label_spaces to select label_values_to_select = {} for label in initial_labels: - if label in list(label_space.keys()): + if label in list(label_space): values = label_space[label] if not isinstance(values, list): values = [values] diff --git a/src/ansys/dpf/post/named_selection.py b/src/ansys/dpf/post/named_selection.py index 3dd67e870..65e9f06b0 100644 --- a/src/ansys/dpf/post/named_selection.py +++ b/src/ansys/dpf/post/named_selection.py @@ -58,7 +58,7 @@ def __len__(self) -> int: def __delitem__(self, __key): """Not implemented.""" - pass + return NotImplementedError def __iter__(self) -> NamedSelectionsIterator: """Returns an iterator to access this dictionary.""" @@ -87,7 +87,7 @@ def id(self, index: int) -> int: """Retrieve the ID at a given index.""" return self._scoping.id(index) - def index(self, id: int) -> int: + def index(self, id: int) -> int: # noqa: W0622 """Retrieve the index of a given ID.""" return self._scoping.index(id) diff --git a/src/ansys/dpf/post/nodes.py b/src/ansys/dpf/post/nodes.py index 268d641e1..0627694e9 100644 --- a/src/ansys/dpf/post/nodes.py +++ b/src/ansys/dpf/post/nodes.py @@ -107,7 +107,7 @@ def _short_list(self) -> str: _str += f"{_fst}, ..., {_lst}" else: el_list = [Node(self._nodes, idx) for idx in range(self.__len__())] - _str += ", ".join(map(lambda el: repr(el), el_list)) + _str += ", ".join(map(repr, el_list)) _str += "]" return _str @@ -127,7 +127,7 @@ def __init__(self, nodes: nodes.Nodes): """Constructs a list from an existing core.nodes.Nodes object.""" super().__init__(nodes) - def __getitem__(self, id: int) -> Node: + def __getitem__(self, id: int) -> Node: # noqa: W0622 """Returns a Node for a given ID.""" idx = self._nodes.scoping.index(id) return super().__getitem__(idx) diff --git a/src/ansys/dpf/post/prop_fields_container.py b/src/ansys/dpf/post/prop_fields_container.py index c10cd30bb..8c6a33656 100644 --- a/src/ansys/dpf/post/prop_fields_container.py +++ b/src/ansys/dpf/post/prop_fields_container.py @@ -10,8 +10,6 @@ from ansys.dpf.core.property_field import PropertyField import numpy as np -from ansys import dpf - class _LabelSpaceKV: """Class for internal use to associate a label space with a field.""" @@ -72,11 +70,11 @@ def __init__(self, fields_container=None, server=None): # Collection def __str__(self): """Returns a string representation of a PropertyFieldsContainer.""" - str = f"DPF PropertyFieldsContainer with {len(self)} fields\n" + txt = f"DPF PropertyFieldsContainer with {len(self)} fields\n" for idx, ls in enumerate(self.label_spaces): - str += f"\t {idx}: {ls}\n" + txt += f"\t {idx}: {ls}\n" - return str + return txt @property def labels(self): diff --git a/tests/test_meshes.py b/tests/test_meshes.py index 47275b4ee..08d45a0df 100644 --- a/tests/test_meshes.py +++ b/tests/test_meshes.py @@ -31,7 +31,7 @@ def test_meshes_get_item(meshes): with pytest.raises( ValueError, match="Access to a specific Mesh of a Meshes requires" ): - meshes["test"] + _ = meshes["test"] mesh1 = meshes[1] assert isinstance(mesh1, post.Mesh) assert len(mesh1.node_ids) == 240 From b2611c88d3a9628deead4d1caa38e5b70fe7f688 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Fri, 7 Jul 2023 21:37:28 +0200 Subject: [PATCH 63/99] Fix code quality --- src/ansys/dpf/post/connectivity.py | 6 +++--- src/ansys/dpf/post/elements.py | 14 +++++++------- src/ansys/dpf/post/mesh.py | 6 ++++-- src/ansys/dpf/post/named_selection.py | 2 +- src/ansys/dpf/post/nodes.py | 2 +- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/ansys/dpf/post/connectivity.py b/src/ansys/dpf/post/connectivity.py index 9ff4c2810..bd00a5a79 100644 --- a/src/ansys/dpf/post/connectivity.py +++ b/src/ansys/dpf/post/connectivity.py @@ -4,7 +4,7 @@ from collections.abc import Collection, Iterator from enum import Enum -from typing import List +from typing import List, Union from ansys.dpf.core.any import Any from ansys.dpf.core.property_field import PropertyField @@ -65,7 +65,7 @@ def __next__(self) -> Any: elif self._mode == ReturnMode.IDX: out = self._get_idx_from_idx(self._idx) else: - raise ValueError(f"ReturnMode has an incorrect value") + raise ValueError("ReturnMode has an incorrect value") self._idx += 1 return out @@ -145,7 +145,7 @@ def __init__(self, field: PropertyField, scoping: Scoping, mode: ReturnMode): """Constructs a Connectivity list from a given PropertyField.""" super().__init__(field, scoping, mode) - def __getitem__(self, id: int) -> List[int]: # noqa: W0622 + def __getitem__(self, id: int) -> List[int]: # pylint: disable=redefined-builtin """Returns a list of indexes or IDs for a given ID, see ReturnMode Enum.""" idx = self._field.scoping.index(id) return super().__getitem__(idx) diff --git a/src/ansys/dpf/post/elements.py b/src/ansys/dpf/post/elements.py index bfd3a967d..db3950a6e 100644 --- a/src/ansys/dpf/post/elements.py +++ b/src/ansys/dpf/post/elements.py @@ -6,7 +6,7 @@ from typing import List, Union import ansys.dpf.core as dpf -from ansys.dpf.core.elements import Element, Elements +from ansys.dpf.core import elements from ansys.dpf.core.nodes import Node @@ -91,12 +91,12 @@ def __repr__(self): class Element: """Proxy class wrapping dpf.core.elements.Element.""" - def __init__(self, elements: Elements, index: int): + def __init__(self, elements: elements.Elements, index: int): """Constructs a Proxy Element object.""" self._elements = elements self._index = index - def _resolve(self) -> Element: + def _resolve(self) -> elements.Element: """Returns the original Element object in the original list.""" return self._elements[self._index] @@ -157,7 +157,7 @@ def __str__(self) -> str: class ElementListIterator(Iterator): """Iterator class for the ElementList.""" - def __init__(self, el_list: Elements): + def __init__(self, el_list: elements.Elements): """Constructs an Iterator from an element list.""" self._el_list = el_list self._idx = 0 @@ -179,7 +179,7 @@ def __iter__(self) -> Iterator: class ElementListIdx(Collection): """List of Elements.""" - def __init__(self, elements: Elements): + def __init__(self, elements: elements.Elements): """Constructs list from existing dpf.core.elements.Elements list.""" self._elements = elements @@ -228,11 +228,11 @@ def __repr__(self) -> str: class ElementListById(ElementListIdx): """Wrapper class for accessing Elements by ID instead of index.""" - def __init__(self, elements: Elements): + def __init__(self, elements: elements.Elements): """Constructs an ElementListById from an Elements instance.""" super().__init__(elements) - def __getitem__(self, id: int) -> Element: # noqa: W0622 + def __getitem__(self, id: int) -> Element: # pylint: disable=redefined-builtin """Access an Element with an ID.""" idx = self._elements.scoping.index(id) return super().__getitem__(idx) diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index 5339078b6..7e786c7b3 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -61,7 +61,9 @@ def elements(self) -> ElementListIdx: """Returns a list of elements indexed by ID.""" return ElementListIdx(self._meshed_region.elements) - def get_element_by_id(self, id: int) -> Element: # noqa: W0622 + def get_element_by_id( + self, id: int + ) -> Element: # pylint: disable=redefined-builtin """Returns an element in the mesh from a given ID.""" return self.elements.by_id[id] @@ -70,7 +72,7 @@ def nodes(self) -> NodeListIdx: """Returns a list of nodes indexed by ID.""" return NodeListIdx(self._meshed_region.nodes) - def get_node_by_id(self, id: int) -> Node: # noqa: W0622 + def get_node_by_id(self, id: int) -> Node: # pylint: disable=redefined-builtin """Returns a node of the mesh from a given ID.""" return self.nodes.by_id[id] diff --git a/src/ansys/dpf/post/named_selection.py b/src/ansys/dpf/post/named_selection.py index 65e9f06b0..5b8efa04c 100644 --- a/src/ansys/dpf/post/named_selection.py +++ b/src/ansys/dpf/post/named_selection.py @@ -87,7 +87,7 @@ def id(self, index: int) -> int: """Retrieve the ID at a given index.""" return self._scoping.id(index) - def index(self, id: int) -> int: # noqa: W0622 + def index(self, id: int) -> int: # pylint: disable=redefined-builtin """Retrieve the index of a given ID.""" return self._scoping.index(id) diff --git a/src/ansys/dpf/post/nodes.py b/src/ansys/dpf/post/nodes.py index 0627694e9..081b722db 100644 --- a/src/ansys/dpf/post/nodes.py +++ b/src/ansys/dpf/post/nodes.py @@ -127,7 +127,7 @@ def __init__(self, nodes: nodes.Nodes): """Constructs a list from an existing core.nodes.Nodes object.""" super().__init__(nodes) - def __getitem__(self, id: int) -> Node: # noqa: W0622 + def __getitem__(self, id: int) -> Node: # pylint: disable=redefined-builtin """Returns a Node for a given ID.""" idx = self._nodes.scoping.index(id) return super().__getitem__(idx) From cfacf69d268649aa6824bf0b071914a6b4929c42 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Fri, 7 Jul 2023 22:04:31 +0200 Subject: [PATCH 64/99] Fix code quality --- src/ansys/dpf/post/common.py | 4 +++- src/ansys/dpf/post/mesh.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ansys/dpf/post/common.py b/src/ansys/dpf/post/common.py index d62974af0..cef7a7497 100644 --- a/src/ansys/dpf/post/common.py +++ b/src/ansys/dpf/post/common.py @@ -4,7 +4,9 @@ ------ """ -from ansys.dpf.core.common import elemental_properties # noqa: F401, W0611 +from ansys.dpf.core.common import ( # noqa: F401 # pylint: disable=W0611 + elemental_properties, +) from ansys.dpf.post.harmonic_mechanical_simulation import HarmonicMechanicalSimulation from ansys.dpf.post.modal_mechanical_simulation import ModalMechanicalSimulation diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index 7e786c7b3..390345e5c 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -62,8 +62,8 @@ def elements(self) -> ElementListIdx: return ElementListIdx(self._meshed_region.elements) def get_element_by_id( - self, id: int - ) -> Element: # pylint: disable=redefined-builtin + self, id: int # pylint: disable=redefined-builtin + ) -> Element: """Returns an element in the mesh from a given ID.""" return self.elements.by_id[id] From b7ec61dd13e1c6a7053fc171d2939d6fea70923f Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 10 Jul 2023 09:27:41 +0200 Subject: [PATCH 65/99] Add test_connectivity.py/test_connectivity_connectivity_list_idx --- src/ansys/dpf/post/connectivity.py | 10 +++++----- tests/test_connectivity.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 tests/test_connectivity.py diff --git a/src/ansys/dpf/post/connectivity.py b/src/ansys/dpf/post/connectivity.py index bd00a5a79..ef2658a99 100644 --- a/src/ansys/dpf/post/connectivity.py +++ b/src/ansys/dpf/post/connectivity.py @@ -26,12 +26,12 @@ def __init__(self, conn_list: PropertyField): self._conn_list = conn_list self._idx = 0 - def __next__(self) -> Any: + def __next__(self) -> List[int]: """Returns the next element in the list.""" if self._idx >= self._conn_list.__len__(): raise StopIteration - ret = self._conn_list.get_entity_data(self._idx) + ret = self._conn_list.get_entity_data(self._idx).tolist() self._idx += 1 return ret @@ -46,8 +46,8 @@ class ConnectivityListIdx(Collection): def __init__( self, field: PropertyField, + mode: ReturnMode, scoping: Union[Scoping, None] = None, - mode: Union[ReturnMode, None] = None, ): """Constructs a ConnectivityList by wrapping given PropertyField.""" self._field = field @@ -83,7 +83,7 @@ def _get_ids_from_idx(self, idx: int) -> List[int]: def _get_idx_from_idx(self, idx: int) -> List[int]: """Helper method to retrieve list of indexes from a given index.""" - return self._field.get_entity_data(idx) + return self._field.get_entity_data(idx).tolist() @property def by_id(self) -> ConnectivityListById: @@ -108,7 +108,7 @@ def __iter__(self) -> ConnectivityListIterator: def __len__(self) -> int: """Returns the number of entities.""" - return len(self._field._get_data_pointer()) + return self._field.scoping.size def _short_list(self) -> str: _str = "[" diff --git a/tests/test_connectivity.py b/tests/test_connectivity.py new file mode 100644 index 000000000..379bca00e --- /dev/null +++ b/tests/test_connectivity.py @@ -0,0 +1,28 @@ +from ansys.dpf import core as dpf +from ansys.dpf.post import connectivity + + +def test_connectivity_list_iterator(): + property_field = dpf.property_field.PropertyField() + cli = connectivity.ConnectivityListIterator(conn_list=property_field) + for i in cli: + assert isinstance(i, connectivity.ConnectivityListIterator) + + +def test_connectivity_connectivity_list_idx(): + property_field = dpf.property_field.PropertyField() + property_field.append(10, scopingid=1) + property_field.append(11, scopingid=2) + property_field.append(12, scopingid=3) + # scoping = dpf.mesh_scoping_factory.nodal_scoping([1, 2, 3]) + cli = connectivity.ConnectivityListIdx( + field=property_field, mode=connectivity.ReturnMode.IDX + ) + for i in cli: + assert isinstance(i, list) + assert cli[1] == [11] + assert len(cli) == 3 + ref = "ConnectivityListIdx([[10], [11], [12]],__len__=3)" + assert repr(cli) == ref + ref = "[[10], [11], [12]]" + assert str(cli) == ref From 80bd4b69b3e6cf5df5ff39c86b76a6e67355b4db Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 10 Jul 2023 09:57:59 +0200 Subject: [PATCH 66/99] Fix calls to ConnectivityListIdx and ConnectivityListIds --- src/ansys/dpf/post/connectivity.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/ansys/dpf/post/connectivity.py b/src/ansys/dpf/post/connectivity.py index ef2658a99..cc430acd9 100644 --- a/src/ansys/dpf/post/connectivity.py +++ b/src/ansys/dpf/post/connectivity.py @@ -37,7 +37,7 @@ def __next__(self) -> List[int]: def __iter__(self) -> ConnectivityListIterator: """Returns a new ConnectivityListIterator.""" - return ConnectivityListIdx(field=self._conn_list) + return ConnectivityListIdx(field=self._conn_list, mode=ReturnMode.IDS) class ConnectivityListIdx(Collection): @@ -88,7 +88,9 @@ def _get_idx_from_idx(self, idx: int) -> List[int]: @property def by_id(self) -> ConnectivityListById: """Returns an equivalent list which accepts an ID instead of an index in __getitem__.""" - return ConnectivityListById(self._field, self._scoping, self._mode) + return ConnectivityListById( + field=self._field, mode=self._mode, scoping=self._scoping + ) def _to_ids(self, indices) -> List[int]: """Helper method to convert a list of indexes into a list of IDs.""" @@ -141,7 +143,12 @@ def __repr__(self) -> str: class ConnectivityListById(ConnectivityListIdx): """Connectivity List indexed by ID.""" - def __init__(self, field: PropertyField, scoping: Scoping, mode: ReturnMode): + def __init__( + self, + field: PropertyField, + mode: ReturnMode, + scoping: Union[Scoping, None] = None, + ): """Constructs a Connectivity list from a given PropertyField.""" super().__init__(field, scoping, mode) From 44e00ad0341362c7b87dad3d04bcfee2b2eb7dc1 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 10 Jul 2023 10:45:04 +0200 Subject: [PATCH 67/99] Fix Mesh.element_to_node_ids_connectivity, Mesh.node_to_element_ids_connectivity, Mesh.element_to_node_connectivity, and Mesh.node_to_element_connectivity --- src/ansys/dpf/post/mesh.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index 390345e5c..bd5ce1312 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -125,28 +125,36 @@ def element_to_node_ids_connectivity(self): """Returns a list of Node ID for a given Element index.""" conn_field = self._meshed_region.elements.connectivities_field nodes_scoping = self._meshed_region.nodes.scoping - return ConnectivityListIdx(conn_field, nodes_scoping, ReturnMode.IDS) + return ConnectivityListIdx( + field=conn_field, mode=ReturnMode.IDS, scoping=nodes_scoping + ) @property def node_to_element_ids_connectivity(self): """Returns a list of Element ID for a given Node index.""" conn_field = self._meshed_region.nodes.nodal_connectivity_field elems_scoping = self._meshed_region.elements.scoping - return ConnectivityListIdx(conn_field, elems_scoping, ReturnMode.IDS) + return ConnectivityListIdx( + field=conn_field, mode=ReturnMode.IDS, scoping=elems_scoping + ) @property def element_to_node_connectivity(self): """Returns a list of Node index for a given Element index.""" conn_field = self._meshed_region.elements.connectivities_field nodes_scoping = self._meshed_region.nodes.scoping - return ConnectivityListIdx(conn_field, nodes_scoping, ReturnMode.IDX) + return ConnectivityListIdx( + field=conn_field, mode=ReturnMode.IDX, scoping=nodes_scoping + ) @property def node_to_element_connectivity(self): """Returns a list of Element index for a given Node index.""" conn_field = self._meshed_region.nodes.nodal_connectivity_field elems_scoping = self._meshed_region.elements.scoping - return ConnectivityListIdx(conn_field, elems_scoping, ReturnMode.IDX) + return ConnectivityListIdx( + field=conn_field, mode=ReturnMode.IDX, scoping=elems_scoping + ) @property def unit(self) -> str: From 8d261301da8db92d1b04392877d032c30a5f2ecc Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 10 Jul 2023 11:09:30 +0200 Subject: [PATCH 68/99] Fix connectivity.py/ConnectivityListById init --- src/ansys/dpf/post/connectivity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/dpf/post/connectivity.py b/src/ansys/dpf/post/connectivity.py index cc430acd9..57cf70a80 100644 --- a/src/ansys/dpf/post/connectivity.py +++ b/src/ansys/dpf/post/connectivity.py @@ -150,7 +150,7 @@ def __init__( scoping: Union[Scoping, None] = None, ): """Constructs a Connectivity list from a given PropertyField.""" - super().__init__(field, scoping, mode) + super().__init__(field=field, mode=mode, scoping=scoping) def __getitem__(self, id: int) -> List[int]: # pylint: disable=redefined-builtin """Returns a list of indexes or IDs for a given ID, see ReturnMode Enum.""" From 22f92cfa5a818542ecae0a151d9038d6568e3b83 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 10 Jul 2023 11:10:09 +0200 Subject: [PATCH 69/99] Fix code quality in 05-mesh-exploration.py --- .../05-mesh-exploration.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/01-Detailed-Examples/05-mesh-exploration.py b/examples/01-Detailed-Examples/05-mesh-exploration.py index 004b7eff0..664b80c11 100644 --- a/examples/01-Detailed-Examples/05-mesh-exploration.py +++ b/examples/01-Detailed-Examples/05-mesh-exploration.py @@ -112,20 +112,20 @@ # ---------------------------------------------- # Get the nodes of an element -mesh.elements[0].nodes +elem_0_nodes = mesh.elements[0].nodes # Get the node IDs of an element -mesh.elements[0].node_ids +elem_0_nodes_ids = mesh.elements[0].node_ids -# Get the nodes of an element -mesh.elements[0].num_nodes +# Get the number of nodes of an element +num_nodes_elem_0 = mesh.elements[0].num_nodes # Get the type of the element -mesh.elements[0].type_info -mesh.elements[0].type +elem_0_type_info = mesh.elements[0].type_info +elem_0_type = mesh.elements[0].type # Get the shape of the element -mesh.elements[0].shape +elem_0_shape = mesh.elements[0].shape ############################################################################### # Get the elemental connectivity @@ -139,7 +139,7 @@ el_idx_5 = mesh.elements[5] # get node IDS from element ID -conn2.by_id[el_idx_5.id] +node_ids = conn2.by_id[el_idx_5.id] ############################################################################### # Get nodes @@ -159,7 +159,7 @@ # ------------------------------------------- # Coordinates -mesh.nodes[1].coordinates +node_1 = mesh.nodes[1].coordinates # Get Nodal connectivity conn3 = mesh.node_to_element_connectivity From a92550898e540a29d583866d4cfa9957a2a88d03 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 10 Jul 2023 15:46:35 +0200 Subject: [PATCH 70/99] WIP --- src/ansys/dpf/post/connectivity.py | 40 +++++++++++++++--------------- src/ansys/dpf/post/mesh.py | 2 +- tests/test_connectivity.py | 28 +++++++++++++++++++++ 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/src/ansys/dpf/post/connectivity.py b/src/ansys/dpf/post/connectivity.py index 57cf70a80..6efc1c972 100644 --- a/src/ansys/dpf/post/connectivity.py +++ b/src/ansys/dpf/post/connectivity.py @@ -2,11 +2,10 @@ from __future__ import annotations -from collections.abc import Collection, Iterator +from collections.abc import Iterator from enum import Enum -from typing import List, Union +from typing import List -from ansys.dpf.core.any import Any from ansys.dpf.core.property_field import PropertyField from ansys.dpf.core.scoping import Scoping @@ -40,23 +39,35 @@ def __iter__(self) -> ConnectivityListIterator: return ConnectivityListIdx(field=self._conn_list, mode=ReturnMode.IDS) -class ConnectivityListIdx(Collection): +class ConnectivityListIdx: """Very basic wrapper around elemental and nodal connectivities fields.""" def __init__( self, field: PropertyField, mode: ReturnMode, - scoping: Union[Scoping, None] = None, + scoping: Scoping, ): - """Constructs a ConnectivityList by wrapping given PropertyField.""" + """Constructs a ConnectivityList by wrapping given PropertyField. + + Arguments: + --------- + field: + Field of connectivity. + mode: + Whether to return indexes or IDs. + scoping: + Element or node scoping to map returned indexes to IDs. + """ self._field = field + if mode not in [ReturnMode.IDS, ReturnMode.IDX]: + raise ValueError("'mode' argument must be a valid ReturnMode value") self._mode = mode self._scoping = scoping self._idx = 0 self.local_scoping = None - def __next__(self) -> Any: + def __next__(self) -> List[int]: """Returns the next element in the list.""" if self._idx >= len(self): raise StopIteration @@ -64,8 +75,6 @@ def __next__(self) -> Any: out = self._get_ids_from_idx(self._idx) elif self._mode == ReturnMode.IDX: out = self._get_idx_from_idx(self._idx) - else: - raise ValueError("ReturnMode has an incorrect value") self._idx += 1 return out @@ -75,7 +84,6 @@ def __getitem__(self, idx: int) -> List[int]: return self._get_ids_from_idx(idx) elif self._mode == ReturnMode.IDX: return self._get_idx_from_idx(idx) - raise ValueError("ReturnMode has an incorrect value") def _get_ids_from_idx(self, idx: int) -> List[int]: """Helper method to retrieve list of IDs from a given index.""" @@ -100,10 +108,6 @@ def _to_ids(self, indices) -> List[int]: to_id = self.local_scoping.id return list(map(to_id, indices)) - def __contains__(self, l: List[int]) -> bool: - """Not implemented.""" - raise NotImplementedError - def __iter__(self) -> ConnectivityListIterator: """Returns an iterator object on the list.""" return ConnectivityListIterator(self._field) @@ -147,7 +151,7 @@ def __init__( self, field: PropertyField, mode: ReturnMode, - scoping: Union[Scoping, None] = None, + scoping: Scoping, ): """Constructs a Connectivity list from a given PropertyField.""" super().__init__(field=field, mode=mode, scoping=scoping) @@ -157,17 +161,13 @@ def __getitem__(self, id: int) -> List[int]: # pylint: disable=redefined-builti idx = self._field.scoping.index(id) return super().__getitem__(idx) - def __contains__(self, l: List[int]) -> bool: - """Not Implemented.""" - raise NotImplementedError - def __iter__(self) -> ConnectivityListIterator: """Returns an iterator object on the list.""" return super().__iter__() def __len__(self) -> int: """Returns the number of entities.""" - return len(self._field._get_data_pointer()) + return self._field.scoping.size def __repr__(self) -> str: """String representation of ConnectivityListById.""" diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index bd5ce1312..e1ef1d608 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -122,7 +122,7 @@ def materials(self) -> post.DataFrame: @property def element_to_node_ids_connectivity(self): - """Returns a list of Node ID for a given Element index.""" + """Returns a.""" conn_field = self._meshed_region.elements.connectivities_field nodes_scoping = self._meshed_region.nodes.scoping return ConnectivityListIdx( diff --git a/tests/test_connectivity.py b/tests/test_connectivity.py index 379bca00e..581dd1a7d 100644 --- a/tests/test_connectivity.py +++ b/tests/test_connectivity.py @@ -1,3 +1,5 @@ +import pytest + from ansys.dpf import core as dpf from ansys.dpf.post import connectivity @@ -14,6 +16,8 @@ def test_connectivity_connectivity_list_idx(): property_field.append(10, scopingid=1) property_field.append(11, scopingid=2) property_field.append(12, scopingid=3) + with pytest.raises(ValueError, match="'mode' argument must be"): + _ = connectivity.ConnectivityListIdx(field=property_field, mode="test") # scoping = dpf.mesh_scoping_factory.nodal_scoping([1, 2, 3]) cli = connectivity.ConnectivityListIdx( field=property_field, mode=connectivity.ReturnMode.IDX @@ -26,3 +30,27 @@ def test_connectivity_connectivity_list_idx(): assert repr(cli) == ref ref = "[[10], [11], [12]]" assert str(cli) == ref + cli2 = cli.by_id + assert isinstance(cli2, connectivity.ConnectivityListById) + + cli = connectivity.ConnectivityListIdx( + field=property_field, mode=connectivity.ReturnMode.IDS + ) + for i in cli: + assert isinstance(i, list) + + assert [10] in cli + + +def test_connectivity_connectivity_list_by_id(): + property_field = dpf.property_field.PropertyField() + property_field.append(10, scopingid=1) + property_field.append(11, scopingid=2) + property_field.append(12, scopingid=3) + cli = connectivity.ConnectivityListById( + field=property_field, + mode=connectivity.ReturnMode.IDS, # , scoping=property_field.scoping + ) + assert cli[1] == [10] + for i in cli: + assert isinstance(i, list) From 4438389f29e0c0b7219bd79e89aa6759f3f8c893 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 10 Jul 2023 19:18:08 +0200 Subject: [PATCH 71/99] Refactor --- src/ansys/dpf/post/connectivity.py | 142 ++++++++++++----------------- src/ansys/dpf/post/mesh.py | 10 +- tests/test_connectivity.py | 68 ++++++++------ 3 files changed, 103 insertions(+), 117 deletions(-) diff --git a/src/ansys/dpf/post/connectivity.py b/src/ansys/dpf/post/connectivity.py index 6efc1c972..8f7ebed4b 100644 --- a/src/ansys/dpf/post/connectivity.py +++ b/src/ansys/dpf/post/connectivity.py @@ -2,7 +2,7 @@ from __future__ import annotations -from collections.abc import Iterator +from abc import ABC, abstractmethod from enum import Enum from typing import List @@ -11,36 +11,14 @@ class ReturnMode(Enum): - """Enum made for internal use, to dictate the behavior of ConnectivityList.""" + """Enum made for internal use, to dictate the behavior of _ConnectivityList.""" IDS = 1 IDX = 2 -class ConnectivityListIterator(Iterator): - """Iterator class for Connectivity Lists.""" - - def __init__(self, conn_list: PropertyField): - """Constructs an iterator from an existing list.""" - self._conn_list = conn_list - self._idx = 0 - - def __next__(self) -> List[int]: - """Returns the next element in the list.""" - if self._idx >= self._conn_list.__len__(): - raise StopIteration - - ret = self._conn_list.get_entity_data(self._idx).tolist() - self._idx += 1 - return ret - - def __iter__(self) -> ConnectivityListIterator: - """Returns a new ConnectivityListIterator.""" - return ConnectivityListIdx(field=self._conn_list, mode=ReturnMode.IDS) - - -class ConnectivityListIdx: - """Very basic wrapper around elemental and nodal connectivities fields.""" +class _ConnectivityList(ABC): + """Abstract class for ConnectivityList objects.""" def __init__( self, @@ -48,7 +26,7 @@ def __init__( mode: ReturnMode, scoping: Scoping, ): - """Constructs a ConnectivityList by wrapping given PropertyField. + """Constructs a _ConnectivityList by wrapping the given PropertyField. Arguments: --------- @@ -71,51 +49,25 @@ def __next__(self) -> List[int]: """Returns the next element in the list.""" if self._idx >= len(self): raise StopIteration - if self._mode == ReturnMode.IDS: - out = self._get_ids_from_idx(self._idx) - elif self._mode == ReturnMode.IDX: - out = self._get_idx_from_idx(self._idx) + out = self.__getitem__(self._idx) self._idx += 1 return out def __getitem__(self, idx: int) -> List[int]: - """Returns a list of indexes or IDs for a given index, see ReturnMode Enum.""" + """Returns, for a given entity index, the connected indexes or IDs (see ReturnMode).""" if self._mode == ReturnMode.IDS: return self._get_ids_from_idx(idx) elif self._mode == ReturnMode.IDX: return self._get_idx_from_idx(idx) - def _get_ids_from_idx(self, idx: int) -> List[int]: - """Helper method to retrieve list of IDs from a given index.""" - return self._to_ids(self._get_idx_from_idx(idx)) - - def _get_idx_from_idx(self, idx: int) -> List[int]: - """Helper method to retrieve list of indexes from a given index.""" - return self._field.get_entity_data(idx).tolist() - - @property - def by_id(self) -> ConnectivityListById: - """Returns an equivalent list which accepts an ID instead of an index in __getitem__.""" - return ConnectivityListById( - field=self._field, mode=self._mode, scoping=self._scoping - ) - - def _to_ids(self, indices) -> List[int]: - """Helper method to convert a list of indexes into a list of IDs.""" - if not self.local_scoping: - self.local_scoping = self._scoping.as_local_scoping() - - to_id = self.local_scoping.id - return list(map(to_id, indices)) - - def __iter__(self) -> ConnectivityListIterator: - """Returns an iterator object on the list.""" - return ConnectivityListIterator(self._field) - def __len__(self) -> int: """Returns the number of entities.""" return self._field.scoping.size + def __repr__(self) -> str: + """Returns string representation of a _ConnectivityList object.""" + return f"{self.__class__.__name__}({self.__str__()}, __len__={self.__len__()})" + def _short_list(self) -> str: _str = "[" if self.__len__() > 3: @@ -136,39 +88,59 @@ def _short_list(self) -> str: return _str def __str__(self) -> str: - """Returns string representation of ConnectivityListIdx.""" + """Returns string representation of a _ConnectivityList object.""" return self._short_list() - def __repr__(self) -> str: - """Returns string representation of ConnectivityListIdx.""" - return f"ConnectivityListIdx({self.__str__()},__len__={self.__len__()})" + def _get_ids_from_idx(self, idx: int) -> List[int]: + """Helper method to retrieve list of IDs from a given index.""" + return self._to_ids(self._get_idx_from_idx(idx)) + def _get_idx_from_idx(self, idx: int) -> List[int]: + """Helper method to retrieve list of indexes from a given index.""" + return self._field.get_entity_data(idx).tolist() -class ConnectivityListById(ConnectivityListIdx): - """Connectivity List indexed by ID.""" + def _to_ids(self, indices) -> List[int]: + """Helper method to convert a list of indexes into a list of IDs.""" + if not self.local_scoping: + self.local_scoping = self._scoping.as_local_scoping() - def __init__( - self, - field: PropertyField, - mode: ReturnMode, - scoping: Scoping, - ): - """Constructs a Connectivity list from a given PropertyField.""" - super().__init__(field=field, mode=mode, scoping=scoping) + to_id = self.local_scoping.id + return list(map(to_id, indices)) + + @abstractmethod + def __iter__(self): + """Returns the object to iterate on.""" + pass + + +class ConnectivityListByIndex(_ConnectivityList): + """Connectivity list object using indexes as input.""" + + @property + def by_id(self) -> ConnectivityListById: + """Returns an equivalent list which accepts IDs as input.""" + return ConnectivityListById( + field=self._field, mode=self._mode, scoping=self._scoping + ) + + def __iter__(self) -> ConnectivityListByIndex: + """Returns the object to iterate on.""" + self._idx = 0 + return self + + +class ConnectivityListById(_ConnectivityList): + """Connectivity list object using IDs as input.""" def __getitem__(self, id: int) -> List[int]: # pylint: disable=redefined-builtin - """Returns a list of indexes or IDs for a given ID, see ReturnMode Enum.""" + """Returns, for a given entity ID, the connected indexes or IDs (see ReturnMode).""" idx = self._field.scoping.index(id) return super().__getitem__(idx) - def __iter__(self) -> ConnectivityListIterator: - """Returns an iterator object on the list.""" - return super().__iter__() - - def __len__(self) -> int: - """Returns the number of entities.""" - return self._field.scoping.size - - def __repr__(self) -> str: - """String representation of ConnectivityListById.""" - return f"ConnectivityListById({super().__str__()}, __len__={self.__len__()})" + def __iter__(self) -> ConnectivityListByIndex: + """Returns the object to iterate on.""" + return ConnectivityListByIndex( + field=self._field, + mode=self._mode, + scoping=self._scoping, + ) diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index e1ef1d608..10b0f57d2 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -13,7 +13,7 @@ import ansys.dpf.post as post from ansys.dpf.post import index, locations -from ansys.dpf.post.connectivity import ConnectivityListIdx, ReturnMode +from ansys.dpf.post.connectivity import ConnectivityListByIndex, ReturnMode from ansys.dpf.post.elements import Element, ElementListIdx from ansys.dpf.post.named_selection import NamedSelectionsDict from ansys.dpf.post.nodes import NodeListIdx @@ -125,7 +125,7 @@ def element_to_node_ids_connectivity(self): """Returns a.""" conn_field = self._meshed_region.elements.connectivities_field nodes_scoping = self._meshed_region.nodes.scoping - return ConnectivityListIdx( + return ConnectivityListByIndex( field=conn_field, mode=ReturnMode.IDS, scoping=nodes_scoping ) @@ -134,7 +134,7 @@ def node_to_element_ids_connectivity(self): """Returns a list of Element ID for a given Node index.""" conn_field = self._meshed_region.nodes.nodal_connectivity_field elems_scoping = self._meshed_region.elements.scoping - return ConnectivityListIdx( + return ConnectivityListByIndex( field=conn_field, mode=ReturnMode.IDS, scoping=elems_scoping ) @@ -143,7 +143,7 @@ def element_to_node_connectivity(self): """Returns a list of Node index for a given Element index.""" conn_field = self._meshed_region.elements.connectivities_field nodes_scoping = self._meshed_region.nodes.scoping - return ConnectivityListIdx( + return ConnectivityListByIndex( field=conn_field, mode=ReturnMode.IDX, scoping=nodes_scoping ) @@ -152,7 +152,7 @@ def node_to_element_connectivity(self): """Returns a list of Element index for a given Node index.""" conn_field = self._meshed_region.nodes.nodal_connectivity_field elems_scoping = self._meshed_region.elements.scoping - return ConnectivityListIdx( + return ConnectivityListByIndex( field=conn_field, mode=ReturnMode.IDX, scoping=elems_scoping ) diff --git a/tests/test_connectivity.py b/tests/test_connectivity.py index 581dd1a7d..0abb1114b 100644 --- a/tests/test_connectivity.py +++ b/tests/test_connectivity.py @@ -4,53 +4,67 @@ from ansys.dpf.post import connectivity -def test_connectivity_list_iterator(): - property_field = dpf.property_field.PropertyField() - cli = connectivity.ConnectivityListIterator(conn_list=property_field) - for i in cli: - assert isinstance(i, connectivity.ConnectivityListIterator) - - def test_connectivity_connectivity_list_idx(): property_field = dpf.property_field.PropertyField() - property_field.append(10, scopingid=1) - property_field.append(11, scopingid=2) - property_field.append(12, scopingid=3) + property_field.append([0, 1, 2], scopingid=1) + property_field.append([2, 3, 4], scopingid=2) + property_field.append([4, 5, 6], scopingid=3) + scoping = dpf.mesh_scoping_factory.nodal_scoping( + [100, 101, 102, 103, 104, 105, 106] + ) with pytest.raises(ValueError, match="'mode' argument must be"): - _ = connectivity.ConnectivityListIdx(field=property_field, mode="test") - # scoping = dpf.mesh_scoping_factory.nodal_scoping([1, 2, 3]) - cli = connectivity.ConnectivityListIdx( - field=property_field, mode=connectivity.ReturnMode.IDX + _ = connectivity.ConnectivityListByIndex( + field=property_field, mode="test", scoping=scoping + ) + cli = connectivity.ConnectivityListByIndex( + field=property_field, mode=connectivity.ReturnMode.IDX, scoping=scoping ) for i in cli: assert isinstance(i, list) - assert cli[1] == [11] + assert cli[1] == [2, 3, 4] assert len(cli) == 3 - ref = "ConnectivityListIdx([[10], [11], [12]],__len__=3)" + ref = "ConnectivityListByIndex([[0 1 2], [2 3 4], [4 5 6]], __len__=3)" assert repr(cli) == ref - ref = "[[10], [11], [12]]" + ref = "[[0 1 2], [2 3 4], [4 5 6]]" assert str(cli) == ref cli2 = cli.by_id assert isinstance(cli2, connectivity.ConnectivityListById) - cli = connectivity.ConnectivityListIdx( - field=property_field, mode=connectivity.ReturnMode.IDS + cli = connectivity.ConnectivityListByIndex( + field=property_field, mode=connectivity.ReturnMode.IDS, scoping=scoping ) for i in cli: assert isinstance(i, list) - - assert [10] in cli + for i in cli: + assert isinstance(i, list) def test_connectivity_connectivity_list_by_id(): property_field = dpf.property_field.PropertyField() - property_field.append(10, scopingid=1) - property_field.append(11, scopingid=2) - property_field.append(12, scopingid=3) + property_field.append([0, 1, 2], scopingid=1) + property_field.append([2, 3, 4], scopingid=2) + property_field.append([4, 5, 6], scopingid=3) + scoping = dpf.mesh_scoping_factory.nodal_scoping( + [100, 101, 102, 103, 104, 105, 106] + ) cli = connectivity.ConnectivityListById( - field=property_field, - mode=connectivity.ReturnMode.IDS, # , scoping=property_field.scoping + field=property_field, mode=connectivity.ReturnMode.IDS, scoping=scoping ) - assert cli[1] == [10] + assert cli[1] == [100, 101, 102] + for i in cli: + assert isinstance(i, list) + for i in cli: + assert isinstance(i, list) + assert len(cli) == 3 + ref = "ConnectivityListById([[100, 101, 102], [102, 103, 104], [104, 105, 106]], __len__=3)" + assert repr(cli) == ref + ref = "[[100, 101, 102], [102, 103, 104], [104, 105, 106]]" + assert str(cli) == ref + + cli = connectivity.ConnectivityListById( + field=property_field, mode=connectivity.ReturnMode.IDS, scoping=scoping + ) + for i in cli: + assert isinstance(i, list) for i in cli: assert isinstance(i, list) From 3e17d35e99e313f08923f06e3cc2024c55e84cb8 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 10 Jul 2023 19:29:23 +0200 Subject: [PATCH 72/99] Improve coverage connectivity.py --- src/ansys/dpf/post/connectivity.py | 2 +- tests/test_connectivity.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ansys/dpf/post/connectivity.py b/src/ansys/dpf/post/connectivity.py index 8f7ebed4b..ac4d88e12 100644 --- a/src/ansys/dpf/post/connectivity.py +++ b/src/ansys/dpf/post/connectivity.py @@ -108,7 +108,7 @@ def _to_ids(self, indices) -> List[int]: return list(map(to_id, indices)) @abstractmethod - def __iter__(self): + def __iter__(self): # pragma: no cover """Returns the object to iterate on.""" pass diff --git a/tests/test_connectivity.py b/tests/test_connectivity.py index 0abb1114b..27831406a 100644 --- a/tests/test_connectivity.py +++ b/tests/test_connectivity.py @@ -44,8 +44,9 @@ def test_connectivity_connectivity_list_by_id(): property_field.append([0, 1, 2], scopingid=1) property_field.append([2, 3, 4], scopingid=2) property_field.append([4, 5, 6], scopingid=3) + property_field.append([6, 7, 8], scopingid=4) scoping = dpf.mesh_scoping_factory.nodal_scoping( - [100, 101, 102, 103, 104, 105, 106] + [100, 101, 102, 103, 104, 105, 106, 107, 108] ) cli = connectivity.ConnectivityListById( field=property_field, mode=connectivity.ReturnMode.IDS, scoping=scoping @@ -55,10 +56,10 @@ def test_connectivity_connectivity_list_by_id(): assert isinstance(i, list) for i in cli: assert isinstance(i, list) - assert len(cli) == 3 - ref = "ConnectivityListById([[100, 101, 102], [102, 103, 104], [104, 105, 106]], __len__=3)" + assert len(cli) == 4 + ref = "ConnectivityListById([[100, 101, 102], ..., [106, 107, 108]], __len__=4)" assert repr(cli) == ref - ref = "[[100, 101, 102], [102, 103, 104], [104, 105, 106]]" + ref = "[[100, 101, 102], ..., [106, 107, 108]]" assert str(cli) == ref cli = connectivity.ConnectivityListById( From 9d19ab67a7e6608763335f37b1c3566a0188a0c0 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 10 Jul 2023 19:52:00 +0200 Subject: [PATCH 73/99] Improve coverage connectivity.py --- tests/test_connectivity.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_connectivity.py b/tests/test_connectivity.py index 27831406a..ca9ca7c98 100644 --- a/tests/test_connectivity.py +++ b/tests/test_connectivity.py @@ -30,6 +30,10 @@ def test_connectivity_connectivity_list_idx(): cli2 = cli.by_id assert isinstance(cli2, connectivity.ConnectivityListById) + property_field.append([6, 7, 8], scopingid=4) + scoping = dpf.mesh_scoping_factory.nodal_scoping( + [100, 101, 102, 103, 104, 105, 106, 107, 108] + ) cli = connectivity.ConnectivityListByIndex( field=property_field, mode=connectivity.ReturnMode.IDS, scoping=scoping ) @@ -37,6 +41,10 @@ def test_connectivity_connectivity_list_idx(): assert isinstance(i, list) for i in cli: assert isinstance(i, list) + ref = "ConnectivityListByIndex([[100, 101, 102], ..., [106, 107, 108]], __len__=4)" + assert repr(cli) == ref + ref = "[[100, 101, 102], ..., [106, 107, 108]]" + assert str(cli) == ref def test_connectivity_connectivity_list_by_id(): From 0f22b79169292247703fe39a28272afd6aff1af0 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 10 Jul 2023 19:59:16 +0200 Subject: [PATCH 74/99] Improve coverage mesh.py --- tests/test_mesh.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/test_mesh.py b/tests/test_mesh.py index 13de94964..5918d2f24 100644 --- a/tests/test_mesh.py +++ b/tests/test_mesh.py @@ -120,3 +120,26 @@ def test_mesh_coordinates(mesh): ... ... ... """ # noqa assert str(coord) == ref + + +def test_mesh_materials(mesh): + materials = mesh.materials + ref = """ + results material_id + element_ids + 5 1 + 6 1 + 1 1 + 2 1 + 7 1 + 8 1 + ... ... +""" # noqa + assert str(materials) == ref + materials_5 = materials.select(element_ids=[5]) + ref = """ + results material_id + element_ids + 5 1 +""" # noqa + assert str(materials_5) == ref From 91ba3aa40c16440e83d8cffc9baa8b980699b614 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 10 Jul 2023 20:00:57 +0200 Subject: [PATCH 75/99] Improve coverage mesh.py --- tests/test_mesh.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_mesh.py b/tests/test_mesh.py index 5918d2f24..7bba357cf 100644 --- a/tests/test_mesh.py +++ b/tests/test_mesh.py @@ -143,3 +143,20 @@ def test_mesh_materials(mesh): 5 1 """ # noqa assert str(materials_5) == ref + + +def test_mesh_element_types(mesh): + element_types = mesh.element_types + print(element_types) + ref = """ + results elem_type_id + element_ids + 5 1 + 6 1 + 1 1 + 2 1 + 7 1 + 8 1 + ... ... +""" # noqa + assert str(element_types) == ref From c09459bfa01d1496221b6c55a41e191ba0bf7992 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 10 Jul 2023 20:38:28 +0200 Subject: [PATCH 76/99] Fix code quality --- src/ansys/dpf/post/connectivity.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ansys/dpf/post/connectivity.py b/src/ansys/dpf/post/connectivity.py index ac4d88e12..06a9adab7 100644 --- a/src/ansys/dpf/post/connectivity.py +++ b/src/ansys/dpf/post/connectivity.py @@ -110,7 +110,6 @@ def _to_ids(self, indices) -> List[int]: @abstractmethod def __iter__(self): # pragma: no cover """Returns the object to iterate on.""" - pass class ConnectivityListByIndex(_ConnectivityList): From f628c3f0228543e66f90fcad6bd815830b0ea3c3 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 11 Jul 2023 10:16:55 +0200 Subject: [PATCH 77/99] Fix 221 retro --- src/ansys/dpf/post/prop_fields_container.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/dpf/post/prop_fields_container.py b/src/ansys/dpf/post/prop_fields_container.py index 8c6a33656..8492254d3 100644 --- a/src/ansys/dpf/post/prop_fields_container.py +++ b/src/ansys/dpf/post/prop_fields_container.py @@ -56,7 +56,7 @@ def __init__(self, fields_container=None, server=None): if fields_container is not None: self._labels = copy.deepcopy(fields_container.labels) # self.scopings = copy.deepcopy(fields_container.scopings) - self._server = copy.deepcopy(fields_container._server) + self._server = fields_container._server # self.ids = copy.deepcopy(fields_container.ids) From 9c6a73205e5641d6f8dd43457cfdf65b079c4c9b Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 11 Jul 2023 11:51:59 +0200 Subject: [PATCH 78/99] Improve coverage test_connectivity.py --- tests/test_connectivity.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_connectivity.py b/tests/test_connectivity.py index ca9ca7c98..fd0feb48b 100644 --- a/tests/test_connectivity.py +++ b/tests/test_connectivity.py @@ -30,6 +30,12 @@ def test_connectivity_connectivity_list_idx(): cli2 = cli.by_id assert isinstance(cli2, connectivity.ConnectivityListById) + cli = connectivity.ConnectivityListByIndex( + field=property_field, mode=connectivity.ReturnMode.IDS, scoping=scoping + ) + ref = "[[100, 101, 102], [102, 103, 104], [104, 105, 106]]" + assert str(cli) == ref + property_field.append([6, 7, 8], scopingid=4) scoping = dpf.mesh_scoping_factory.nodal_scoping( [100, 101, 102, 103, 104, 105, 106, 107, 108] From 7b2dc6d672ffafe8623a85c891db8213c23c50f6 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 11 Jul 2023 13:24:05 +0200 Subject: [PATCH 79/99] Coverage elements.py/ElementType --- src/ansys/dpf/post/elements.py | 4 +-- tests/test_elements.py | 47 ++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 tests/test_elements.py diff --git a/src/ansys/dpf/post/elements.py b/src/ansys/dpf/post/elements.py index db3950a6e..b60c17a2e 100644 --- a/src/ansys/dpf/post/elements.py +++ b/src/ansys/dpf/post/elements.py @@ -23,8 +23,8 @@ def __init__(self, arg: Union[dpf.ElementDescriptor, int]): raise TypeError("Given argument is not an int nor an ElementDescriptor") @property - def enum_id(self) -> int: - """Id of the Element type in the element_types enum.""" + def enum_id(self) -> dpf.element_types: + """ID of the Element type in the element_types enum.""" return self._el_desc.enum_id @property diff --git a/tests/test_elements.py b/tests/test_elements.py new file mode 100644 index 000000000..45624def3 --- /dev/null +++ b/tests/test_elements.py @@ -0,0 +1,47 @@ +import pytest + +from ansys.dpf import core as dpf +from ansys.dpf.post.elements import Element, ElementType + + +def test_element_type(): + with pytest.raises(TypeError, match="Given argument"): + _ = ElementType("test") + + el_type = ElementType( + arg=dpf.ElementDescriptor( + enum_id=dpf.element_types.Beam4, description="test", name="test" + ) + ) + ref = "test" + assert el_type.description == ref + + el_type = ElementType(arg=1) + ref = """Element Type +------------ +Enum id (dpf.element_types): element_types.Hex20 +Element description: Quadratic 20-nodes Hexa +Element name (short): hex20 +Element shape: solid +Number of corner nodes: 8 +Number of mid-side nodes: 12 +Total number of nodes: 20 +Quadratic element: True""" + assert str(el_type) == ref + assert repr(el_type) == ref + ref = "Quadratic 20-nodes Hexa" + assert el_type.description == ref + assert el_type.enum_id == dpf.element_types.Hex20 + assert el_type.name == "hex20" + assert el_type.shape == "solid" + assert el_type.num_corner_nodes == 8 + assert el_type.num_mid_nodes == 12 + assert el_type.num_nodes == 20 + assert el_type.is_quadratic + assert el_type.is_solid + assert not el_type.is_shell + assert not el_type.is_beam + + +def test_element(): + element = Element() From 98a070bb4717a59f68c3bd50ddd23ac5fa25ee1d Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 11 Jul 2023 13:44:45 +0200 Subject: [PATCH 80/99] Coverage elements.py/Element --- src/ansys/dpf/post/elements.py | 35 +++++++++++++++------------------- tests/test_elements.py | 26 ++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/src/ansys/dpf/post/elements.py b/src/ansys/dpf/post/elements.py index b60c17a2e..f2075ff9f 100644 --- a/src/ansys/dpf/post/elements.py +++ b/src/ansys/dpf/post/elements.py @@ -24,7 +24,7 @@ def __init__(self, arg: Union[dpf.ElementDescriptor, int]): @property def enum_id(self) -> dpf.element_types: - """ID of the Element type in the element_types enum.""" + """Element type in the element_types enum.""" return self._el_desc.enum_id @property @@ -91,59 +91,54 @@ def __repr__(self): class Element: """Proxy class wrapping dpf.core.elements.Element.""" - def __init__(self, elements: elements.Elements, index: int): + def __init__(self, element: elements.Element): """Constructs a Proxy Element object.""" - self._elements = elements - self._index = index - - def _resolve(self) -> elements.Element: - """Returns the original Element object in the original list.""" - return self._elements[self._index] + self._element = element @property def node_ids(self) -> List[int]: """See :py:meth:`ansys.dpf.core.elements.Element.node_ids`.""" - return self._resolve().node_ids + return self._element.node_ids @property def id(self) -> int: """See :py:meth:`ansys.dpf.core.elements.Element.id`.""" - return self._resolve().id + return self._element.id @property def index(self) -> int: """See :py:meth:`ansys.dpf.core.elements.Element.index`.""" - return self._resolve().index + return self._element.index @property def nodes(self) -> List[Node]: """See :py:meth:`ansys.dpf.core.elements.Element.nodes`.""" - return self._resolve().nodes + return self._element.nodes @property def num_nodes(self) -> int: """See :py:meth:`ansys.dpf.core.elements.Element.n_nodes`.""" - return self._resolve().n_nodes + return self._element.n_nodes @property def type_info(self) -> ElementType: """Gets an element descriptor, See :py:meth:`ansys.dpf.core.elements.Element.id`.""" - return ElementType(self._resolve().type.value) + return ElementType(self._element.type.value) @property - def type(self) -> int: - """Returns the ID of the Element Type.""" - return self._resolve().type + def type(self) -> elements.element_types: + """Returns the Element Type.""" + return self._element.type @property def shape(self) -> str: """See :py:meth:`ansys.dpf.core.elements.Element.shape`.""" - return self._resolve().shape + return self._element.shape @property def to_node_connectivity(self) -> List[int]: """See :py:meth:`ansys.dpf.core.elements.Element.connectivity`.""" - return self._resolve().connectivity + return self._element.connectivity def __repr__(self) -> str: """Returns string representation of an Element.""" @@ -151,7 +146,7 @@ def __repr__(self) -> str: def __str__(self) -> str: """Returns string representation of an Element.""" - return self._resolve().__str__() + return self._element.__str__() class ElementListIterator(Iterator): diff --git a/tests/test_elements.py b/tests/test_elements.py index 45624def3..665a2be7a 100644 --- a/tests/test_elements.py +++ b/tests/test_elements.py @@ -1,6 +1,8 @@ +from ansys.dpf.core.nodes import Node import pytest from ansys.dpf import core as dpf +from ansys.dpf.post import examples from ansys.dpf.post.elements import Element, ElementType @@ -44,4 +46,26 @@ def test_element_type(): def test_element(): - element = Element() + model = dpf.Model(examples.find_static_rst()) + core_elements = model.metadata.meshed_region.elements + element = Element(element=core_elements[0]) + ref = [1, 26, 14, 12, 2, 27, 15, 13, 33, 64, 59, 30, 37, 65, 61, 34, 28, 81, 63, 58] + assert element.node_ids == ref + assert element.id == 5 + assert element.index == 0 + assert isinstance(element.nodes[0], Node) + assert element.num_nodes == 20 + assert isinstance(element.type_info, ElementType) + assert element.type == dpf.element_types.Hex20 + assert element.shape == "solid" + ref = [0, 25, 13, 11, 1, 26, 14, 12, 32, 63, 58, 29, 36, 64, 60, 33, 27, 80, 62, 57] + assert element.to_node_connectivity == ref + ref = """DPF Element 5 +\tIndex: 0 +\tNodes: 20 +\tType: Hex20 +\tShape: Solid +""" + assert str(element) == ref + ref = "Element(type=element_types.Hex20,index=0,id=5,shape=solid)" + assert repr(element) == ref From 110efbee8b91a702880e0677ccc15c5303c6b52f Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 11 Jul 2023 14:18:26 +0200 Subject: [PATCH 81/99] Refactor and coverage of elements.py --- src/ansys/dpf/post/elements.py | 117 +++++++++++++++------------------ src/ansys/dpf/post/mesh.py | 6 +- tests/test_elements.py | 38 ++++++++++- 3 files changed, 94 insertions(+), 67 deletions(-) diff --git a/src/ansys/dpf/post/elements.py b/src/ansys/dpf/post/elements.py index f2075ff9f..70a09077e 100644 --- a/src/ansys/dpf/post/elements.py +++ b/src/ansys/dpf/post/elements.py @@ -2,11 +2,12 @@ from __future__ import annotations -from collections.abc import Collection, Iterator +from abc import ABC, abstractmethod from typing import List, Union import ansys.dpf.core as dpf -from ansys.dpf.core import elements +from ansys.dpf.core import elements as core_elements +from ansys.dpf.core import errors from ansys.dpf.core.nodes import Node @@ -91,7 +92,7 @@ def __repr__(self): class Element: """Proxy class wrapping dpf.core.elements.Element.""" - def __init__(self, element: elements.Element): + def __init__(self, element: core_elements.Element): """Constructs a Proxy Element object.""" self._element = element @@ -126,7 +127,7 @@ def type_info(self) -> ElementType: return ElementType(self._element.type.value) @property - def type(self) -> elements.element_types: + def type(self) -> core_elements.element_types: """Returns the Element Type.""" return self._element.type @@ -146,104 +147,94 @@ def __repr__(self) -> str: def __str__(self) -> str: """Returns string representation of an Element.""" - return self._element.__str__() + return str(self._element) -class ElementListIterator(Iterator): +class _ElementList(ABC): """Iterator class for the ElementList.""" - def __init__(self, el_list: elements.Elements): + def __init__(self, el_list: core_elements.Elements): """Constructs an Iterator from an element list.""" self._el_list = el_list self._idx = 0 def __next__(self) -> Element: """Returns the next Element in the list.""" - if self._idx >= self._el_list.__len__(): + if self._idx >= len(self._el_list): raise StopIteration - - ret = Element(self._el_list, self._idx) + ret = self[self._idx] self._idx += 1 return ret - def __iter__(self) -> Iterator: - """Returns a new Iterator object.""" - return ElementListIterator(self._el_list) - - -class ElementListIdx(Collection): - """List of Elements.""" - - def __init__(self, elements: elements.Elements): - """Constructs list from existing dpf.core.elements.Elements list.""" - self._elements = elements - def __getitem__(self, index: int) -> Element: - """Delegates to element_by_id() if by_id, otherwise to element_by_index().""" - return Element(self._elements, index) - - def __contains__(self, el: Element) -> bool: - """Checks if the given element in the list.""" - return el.index >= 0 and el.index < self.__len__() - - def __iter__(self) -> ElementListIterator: - """Returns an Iterator object on the list.""" - return ElementListIterator(self._elements) + """Returns a post.Element based on an index in the current list.""" + return Element(self._el_list[index]) def __len__(self) -> int: """Returns the number of elements in the list.""" - return self._elements.n_elements + return self._el_list.n_elements - @property - def by_id(self) -> ElementListById: - """Returns an equivalent list accessible with ID instead of index.""" - return ElementListById(self._elements) + def __repr__(self) -> str: + """Returns a string representation of _ElementList object.""" + return f"{self.__class__.__name__}({self}, __len__={len(self)})" def _short_list(self) -> str: _str = "[" if self.__len__() > 3: - _fst = Element(self._elements, 0).type_info.name - _lst = Element(self._elements, self.__len__() - 1).type_info.name + _fst = Element(self._el_list[0]).type_info.name + _lst = Element(self._el_list[len(self) - 1]).type_info.name _str += f"{_fst}, ..., {_lst}" else: - el_list = [Element(self._elements, idx) for idx in range(self.__len__())] + el_list = [Element(self._el_list[idx]) for idx in range(len(self))] _str += ", ".join(map(lambda el: el.type_info.name, el_list)) _str += "]" return _str def __str__(self) -> str: - """Returns a string representation of ElementListIdx.""" + """Returns a string representation of an _ElementList object.""" return self._short_list() - def __repr__(self) -> str: - """Returns a string representation of ElementListIdx.""" - return f"ElementListIdx({self.__str__()}, __len__={self.__len__()})" + @abstractmethod + def __iter__(self): # pragma: no cover + """Returns the object to iterate on.""" -class ElementListById(ElementListIdx): - """Wrapper class for accessing Elements by ID instead of index.""" +class ElementListByIndex(_ElementList): + """Element list object using indexes as input.""" + + @property + def by_id(self) -> ElementListById: + """Returns an equivalent list which accepts IDs as input.""" + return ElementListById(self._el_list) + + def __iter__(self) -> ElementListByIndex: + """Returns the object to iterate over.""" + self._idx = 0 + return self + + def __contains__(self, el: Element) -> bool: + """Checks if the given element in the list.""" + return len(self) > el.index >= 0 - def __init__(self, elements: elements.Elements): - """Constructs an ElementListById from an Elements instance.""" - super().__init__(elements) + +class ElementListById(_ElementList): + """Element list object using IDs as input.""" def __getitem__(self, id: int) -> Element: # pylint: disable=redefined-builtin """Access an Element with an ID.""" - idx = self._elements.scoping.index(id) - return super().__getitem__(idx) + idx = self._el_list.scoping.index(id) + try: + return super().__getitem__(idx) + except errors.DPFServerException as e: + if "element not found" in str(e): + raise ValueError(f"Element with ID={id} not found in the list.") + else: + raise e # pragma: no cover def __contains__(self, el: Element): """Checks if the given element is in the list.""" - return el.id in self._elements.scoping.ids - - def __iter__(self) -> ElementListIterator: - """Returns an iterator object on the list.""" - return super().__iter__() - - def __len__(self) -> int: - """Returns the number of elements in the list.""" - return self._elements.n_elements + return el.id in self._el_list.scoping.ids - def __repr__(self): - """Returns a string representation of ElementListById.""" - return f"ElementListById({super().__str__()}, __len__={self.__len__()})" + def __iter__(self) -> ElementListByIndex: + """Returns the object to iterate over.""" + return ElementListByIndex(self._el_list) diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index 10b0f57d2..3d4d5e969 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -14,7 +14,7 @@ import ansys.dpf.post as post from ansys.dpf.post import index, locations from ansys.dpf.post.connectivity import ConnectivityListByIndex, ReturnMode -from ansys.dpf.post.elements import Element, ElementListIdx +from ansys.dpf.post.elements import Element, ElementListByIndex from ansys.dpf.post.named_selection import NamedSelectionsDict from ansys.dpf.post.nodes import NodeListIdx from ansys.dpf.post.prop_fields_container import _PropertyFieldsContainer @@ -57,9 +57,9 @@ def num_elements(self) -> int: return len(self.element_ids) @property - def elements(self) -> ElementListIdx: + def elements(self) -> ElementListByIndex: """Returns a list of elements indexed by ID.""" - return ElementListIdx(self._meshed_region.elements) + return ElementListByIndex(self._meshed_region.elements) def get_element_by_id( self, id: int # pylint: disable=redefined-builtin diff --git a/tests/test_elements.py b/tests/test_elements.py index 665a2be7a..cc5014429 100644 --- a/tests/test_elements.py +++ b/tests/test_elements.py @@ -3,7 +3,12 @@ from ansys.dpf import core as dpf from ansys.dpf.post import examples -from ansys.dpf.post.elements import Element, ElementType +from ansys.dpf.post.elements import ( + Element, + ElementListById, + ElementListByIndex, + ElementType, +) def test_element_type(): @@ -69,3 +74,34 @@ def test_element(): assert str(element) == ref ref = "Element(type=element_types.Hex20,index=0,id=5,shape=solid)" assert repr(element) == ref + + +def test_elements_elements_list_by_idx(): + model = dpf.Model(examples.find_static_rst()) + core_elements = model.metadata.meshed_region.elements + elements_list_by_index = ElementListByIndex(el_list=core_elements) + for i in elements_list_by_index: + assert isinstance(i, Element) + for i in elements_list_by_index: + assert isinstance(i, Element) + assert elements_list_by_index[1].id == 6 + assert len(elements_list_by_index) == 8 + ref = "ElementListByIndex([hex20, ..., hex20], __len__=8)" + assert repr(elements_list_by_index) == ref + ref = "[hex20, ..., hex20]" + assert str(elements_list_by_index) == ref + assert elements_list_by_index[0] in elements_list_by_index + elements_list_by_id = elements_list_by_index.by_id + assert isinstance(elements_list_by_id, ElementListById) + + +def test_elements_elements_list_by_id(): + model = dpf.Model(examples.find_static_rst()) + core_elements = model.metadata.meshed_region.elements + elements_list_by_id = ElementListById(el_list=core_elements) + for i in elements_list_by_id: + assert isinstance(i, Element) + assert isinstance(elements_list_by_id[5], Element) + assert elements_list_by_id[5] in elements_list_by_id + with pytest.raises(ValueError, match="not found"): + _ = elements_list_by_id[0] From 2b6b35153782a9acc47de347bed55319bc716c3f Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 11 Jul 2023 14:20:57 +0200 Subject: [PATCH 82/99] Coverage mesh.py --- tests/test_mesh.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_mesh.py b/tests/test_mesh.py index 7bba357cf..d64a35ce8 100644 --- a/tests/test_mesh.py +++ b/tests/test_mesh.py @@ -43,6 +43,8 @@ def test_mesh_named_selections(mesh): def test_mesh_unit(mesh): assert mesh.unit == "m" + mesh.unit = "kg" + assert mesh.unit == "kg" def test_mesh_nodes(mesh): @@ -160,3 +162,7 @@ def test_mesh_element_types(mesh): ... ... """ # noqa assert str(element_types) == ref + + +def test_mesh_plot(mesh): + mesh.plot() From 8725827524c97d66e952c890438a0ada8516329d Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 11 Jul 2023 14:22:35 +0200 Subject: [PATCH 83/99] Coverage selection.py --- src/ansys/dpf/post/selection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/dpf/post/selection.py b/src/ansys/dpf/post/selection.py index ed2420e00..5385b2d83 100644 --- a/src/ansys/dpf/post/selection.py +++ b/src/ansys/dpf/post/selection.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, List -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ansys.dpf.post.simulation import Simulation from ansys.dpf.post.mesh import Mesh From 9ba9345e9e7d1de6eaeda1da066526cc13082bbb Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 11 Jul 2023 15:24:04 +0200 Subject: [PATCH 84/99] Refactor NamedSelections in named_selection.py --- src/ansys/dpf/post/mesh.py | 8 +- src/ansys/dpf/post/named_selection.py | 102 +++++++++++++++----------- 2 files changed, 65 insertions(+), 45 deletions(-) diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index 3d4d5e969..5216fc4f9 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -15,7 +15,7 @@ from ansys.dpf.post import index, locations from ansys.dpf.post.connectivity import ConnectivityListByIndex, ReturnMode from ansys.dpf.post.elements import Element, ElementListByIndex -from ansys.dpf.post.named_selection import NamedSelectionsDict +from ansys.dpf.post.named_selection import NamedSelections from ansys.dpf.post.nodes import NodeListIdx from ansys.dpf.post.prop_fields_container import _PropertyFieldsContainer @@ -32,9 +32,9 @@ def __str__(self): return str(self._meshed_region).replace("Meshed Region", "Mesh") @property - def named_selections(self) -> NamedSelectionsDict: - """Returns a mapping of scopings.""" - return NamedSelectionsDict(self._meshed_region) + def named_selections(self) -> NamedSelections: + """Returns a dictionary of available named selections for this mesh.""" + return NamedSelections(self) @property def node_ids(self) -> List[int]: diff --git a/src/ansys/dpf/post/named_selection.py b/src/ansys/dpf/post/named_selection.py index 5b8efa04c..5a435a37d 100644 --- a/src/ansys/dpf/post/named_selection.py +++ b/src/ansys/dpf/post/named_selection.py @@ -2,46 +2,26 @@ from __future__ import annotations -from collections.abc import Iterator, Mapping +from collections.abc import Mapping import copy from typing import List, Union import ansys.dpf.core as dpf import ansys.dpf.core.dpf_array as dpf_array +from ansys.dpf.post.mesh import Mesh -class NamedSelectionsIterator(Iterator): - """Iterator implementation for NamedSelectionsDict.""" - def __init__(self, ns_dict: NamedSelectionsDict): - """Initialize the Named Selection Iterator. see NamedSelectionsDict.""" - self.idx = 0 - self.ns_dict = ns_dict - - def __iter__(self) -> NamedSelectionsIterator: - """Get base iterator.""" - self.idx = 0 - return self - - def __next__(self) -> str: - """Returns next value.""" - if self.idx < len(self.ns_dict): - res = self.ns_dict.keys()[self.idx] - self.idx += 1 - return res - else: - raise StopIteration +class NamedSelections(Mapping): + """Dictionary of available named selections for a given mesh.""" - -class NamedSelectionsDict(Mapping): - """Proxy class to expose Named Selections interface to post.Mesh.""" - - def __init__(self, meshed_region: dpf.MeshedRegion): - """Initialize Named Selections dictionary from internal Meshed Region.""" - self._meshed_region = meshed_region + def __init__(self, mesh: Mesh): + """Initialize Named Selections dictionary from a Mesh.""" + self._idx = 0 + self._meshed_region = mesh._meshed_region def __getitem__(self, key: str) -> NamedSelection: - """Implements [] getter access function.""" + """Get named selection of name equal to the given key.""" if key in self._meshed_region.available_named_selections: scoping = self._meshed_region.named_selection(key) return NamedSelection(key, scoping) @@ -56,21 +36,61 @@ def __len__(self) -> int: """Returns the length of the dictionary (number of named selections).""" return len(self.keys()) - def __delitem__(self, __key): - """Not implemented.""" - return NotImplementedError + def __iter__(self) -> NamedSelections: + """Get base iterator.""" + self._idx = 0 + return self - def __iter__(self) -> NamedSelectionsIterator: - """Returns an iterator to access this dictionary.""" - return NamedSelectionsIterator(self) + def __next__(self) -> str: + """Returns next value.""" + if self._idx >= len(self): + raise StopIteration + res = self.keys()[self._idx] + self._idx += 1 + return res class NamedSelection: - """Class decorating dpf.Scoping with a name attribute.""" + """Named Selection class associating a name to a list of mesh entities.""" + + def __init__( + self, + name: str, + node_ids: Union[List[int], None] = None, + element_ids: Union[List[int], None] = None, + face_ids: Union[List[int], None] = None, + cell_ids: Union[List[int], None] = None, + scoping: Union[dpf.Scoping, None] = None, + ): + """Constructs a NamedSelection from a name and a list of entity IDs.""" + tot = ( + (node_ids is not None) + + (element_ids is not None) + + (face_ids is not None) + + (cell_ids is not None) + + (scoping is not None) + ) + if tot > 1: + raise ValueError( + "NamedSelection accepts only one argument giving a list of entities." + ) + elif tot == 0: + raise ValueError("No list of entity IDs given.") + if scoping: + self._scoping = scoping + elif node_ids: + self._scoping = dpf.mesh_scoping_factory.nodal_scoping(node_ids=node_ids) + elif element_ids: + self._scoping = dpf.mesh_scoping_factory.elemental_scoping( + element_ids=element_ids + ) + elif face_ids: + self._scoping = dpf.mesh_scoping_factory.face_scoping(face_ids=face_ids) + elif cell_ids: + self._scoping = dpf.mesh_scoping_factory.elemental_scoping( + element_ids=cell_ids + ) - def __init__(self, name: str, scoping: dpf.Scoping): - """Constructs a NamedSelection from a name and a Scoping.""" - self._scoping = scoping self._name = name @property @@ -110,13 +130,13 @@ def deep_copy(self, server=None) -> NamedSelection: """Create a deep copy of the underlying scoping's data on a given server.""" new_scoping = self._scoping.deep_copy(server) new_name = copy.copy(self._name) - return NamedSelection(new_name, new_scoping) + return NamedSelection(name=new_name, scoping=new_scoping) def as_local_scoping(self) -> NamedSelection: """Create a deep copy of the underlying scoping that can be modified locally.""" local_scoping = self._scoping.as_local_scoping() local_name = copy.copy(self._name) - return NamedSelection(local_name, local_scoping) + return NamedSelection(name=local_name, scoping=local_scoping) def __repr__(self) -> str: """Pretty print string of the NamedSelection.""" From 8e5ff1ab0358538cecdbb3578822084be95f25c6 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 11 Jul 2023 16:12:27 +0200 Subject: [PATCH 85/99] Refactor NamedSelections in named_selection.py --- src/ansys/dpf/post/named_selection.py | 5 +++-- tests/test_mesh.py | 10 +++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/ansys/dpf/post/named_selection.py b/src/ansys/dpf/post/named_selection.py index 5a435a37d..b5e0c6b02 100644 --- a/src/ansys/dpf/post/named_selection.py +++ b/src/ansys/dpf/post/named_selection.py @@ -4,12 +4,13 @@ from collections.abc import Mapping import copy -from typing import List, Union +from typing import TYPE_CHECKING, List, Union import ansys.dpf.core as dpf import ansys.dpf.core.dpf_array as dpf_array -from ansys.dpf.post.mesh import Mesh +if TYPE_CHECKING: # pragma: no cover + from ansys.dpf.post.mesh import Mesh class NamedSelections(Mapping): diff --git a/tests/test_mesh.py b/tests/test_mesh.py index d64a35ce8..aa4f3838a 100644 --- a/tests/test_mesh.py +++ b/tests/test_mesh.py @@ -3,6 +3,7 @@ from pytest import fixture from ansys.dpf.post import StaticMechanicalSimulation +from ansys.dpf.post.named_selection import NamedSelection @fixture @@ -34,11 +35,14 @@ def test_mesh_num(mesh): def test_mesh_named_selections(mesh): - ns = mesh.named_selections.keys() + ns = mesh.named_selections assert len(ns) == 1 + assert len(ns.keys()) == 1 assert all([isinstance(n, str) for n in ns]) - assert ns[0] == "_FIXEDSU" - assert len(mesh.named_selections[ns[0]].ids) == 21 + assert ns.keys()[0] == "_FIXEDSU" + n = ns[ns.keys()[0]] + assert isinstance(n, NamedSelection) + assert len(n.ids) == 21 def test_mesh_unit(mesh): From d207d370a7de0e230029d108a31e5a26a0fd0744 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 11 Jul 2023 17:23:17 +0200 Subject: [PATCH 86/99] Add test_named_selection.py --- tests/test_mesh.py | 5 ----- tests/test_named_selection.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 tests/test_named_selection.py diff --git a/tests/test_mesh.py b/tests/test_mesh.py index aa4f3838a..57c93f10f 100644 --- a/tests/test_mesh.py +++ b/tests/test_mesh.py @@ -3,7 +3,6 @@ from pytest import fixture from ansys.dpf.post import StaticMechanicalSimulation -from ansys.dpf.post.named_selection import NamedSelection @fixture @@ -36,12 +35,8 @@ def test_mesh_num(mesh): def test_mesh_named_selections(mesh): ns = mesh.named_selections - assert len(ns) == 1 - assert len(ns.keys()) == 1 - assert all([isinstance(n, str) for n in ns]) assert ns.keys()[0] == "_FIXEDSU" n = ns[ns.keys()[0]] - assert isinstance(n, NamedSelection) assert len(n.ids) == 21 diff --git a/tests/test_named_selection.py b/tests/test_named_selection.py new file mode 100644 index 000000000..b9feefe69 --- /dev/null +++ b/tests/test_named_selection.py @@ -0,0 +1,32 @@ +from pytest import fixture + +from ansys.dpf import core as dpf +from ansys.dpf.post import StaticMechanicalSimulation +from ansys.dpf.post.named_selection import NamedSelection + + +@fixture +def mesh(static_rst): + simulation = StaticMechanicalSimulation(static_rst) + return simulation.mesh + + +def test_named_selections(mesh): + ns = mesh.named_selections + assert len(ns) == 1 + assert len(ns.keys()) == 1 + assert all([isinstance(n, str) for n in ns]) + assert ns.keys()[0] == "_FIXEDSU" + n = ns[ns.keys()[0]] + assert isinstance(n, NamedSelection) + assert len(n.ids) == 21 + + +def test_named_selection(): + scoping = dpf.Scoping(ids=[1, 2, 3], location=dpf.locations.nodal) + ns = NamedSelection(name="test", scoping=scoping) + assert ns._scoping == scoping + assert ns.name == "test" + assert ns.id(0) == 1 + assert ns.index(1) == 0 + assert ns.location == dpf.locations.nodal From 7fd3e0e7858e48e6f1dad3f21f406c77c690e25c Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 11 Jul 2023 19:08:12 +0200 Subject: [PATCH 87/99] Add test_named_selection.py --- src/ansys/dpf/post/named_selection.py | 4 ++++ tests/test_named_selection.py | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/ansys/dpf/post/named_selection.py b/src/ansys/dpf/post/named_selection.py index b5e0c6b02..e63e9e0ef 100644 --- a/src/ansys/dpf/post/named_selection.py +++ b/src/ansys/dpf/post/named_selection.py @@ -94,6 +94,10 @@ def __init__( self._name = name + def __eq__(self, other) -> bool: + """Tests equality of location and IDs.""" + return (self.location == other.location) and (all(self.ids == other.ids)) + @property def name(self) -> str: """Returns the name.""" diff --git a/tests/test_named_selection.py b/tests/test_named_selection.py index b9feefe69..bf016088a 100644 --- a/tests/test_named_selection.py +++ b/tests/test_named_selection.py @@ -1,3 +1,4 @@ +import pytest from pytest import fixture from ansys.dpf import core as dpf @@ -20,6 +21,8 @@ def test_named_selections(mesh): n = ns[ns.keys()[0]] assert isinstance(n, NamedSelection) assert len(n.ids) == 21 + with pytest.raises(KeyError, match="could not be found"): + _ = ns["test"] def test_named_selection(): @@ -30,3 +33,27 @@ def test_named_selection(): assert ns.id(0) == 1 assert ns.index(1) == 0 assert ns.location == dpf.locations.nodal + assert ns.size == 3 + ref = """NamedSelection 'test' + with DPF Scoping: + with Nodal location and 3 entities +""" # noqa + assert repr(ns) == ref + + assert ns.deep_copy() == ns + + assert ns.as_local_scoping() == ns + + with pytest.raises(ValueError, match="No list of entity IDs given."): + _ = NamedSelection(name="test") + with pytest.raises(ValueError, match="NamedSelection accepts only"): + _ = NamedSelection(name="test", node_ids=[0, 1], element_ids=[0, 1]) + + node_ns = NamedSelection(name="test", node_ids=[0, 1]) + assert node_ns._scoping.location == dpf.locations.nodal + element_ns = NamedSelection(name="test", element_ids=[0, 1]) + assert element_ns._scoping.location == dpf.locations.elemental + face_ns = NamedSelection(name="test", face_ids=[0, 1]) + assert face_ns._scoping.location == dpf.locations.faces + cell_ns = NamedSelection(name="test", cell_ids=[0, 1]) + assert cell_ns._scoping.location == dpf.locations.elemental From baeef137c7914ffe8d556b714956694886015357 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Wed, 12 Jul 2023 11:31:03 +0200 Subject: [PATCH 88/99] Update CI to work with 241 dev libraries --- .github/workflows/ci.yml | 19 ++++++++++++------- .github/workflows/examples.yml | 2 ++ .pre-commit-config.yaml | 1 + requirements/requirements_dev.txt | 3 +++ 4 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 requirements/requirements_dev.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 896f8efb3..22d5f7a3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,14 +76,16 @@ jobs: uses: ansys/pydpf-actions/build_package@v2.3 with: python-version: ${{ matrix.python-version }} - ANSYS_VERSION: '241' + ANSYS_VERSION: ${{env.ANSYS_VERSION}} PACKAGE_NAME: ${{env.PACKAGE_NAME}} MODULE: ${{env.MODULE}} dpf-standalone-TOKEN: ${{secrets.DPF_PIPELINE}} install_extras: plotting wheel: true wheelhouse: true - standalone_suffix: ${{ github.event.inputs.standalone_branch_suffix || '' }} + standalone_suffix: ${{ inputs.standalone_suffix || ''}} + extra-pip-args: ${{ './dpf-standalone/v241/dist' && format('--find-links {0}', './dpf-standalone/v241/dist')}} + custom-requirements: requirements/requirements_dev.txt - name: "Prepare Testing Environment" uses: ansys/pydpf-actions/prepare_tests@v2.3 @@ -151,7 +153,6 @@ jobs: dpf-standalone-TOKEN: ${{secrets.DPF_PIPELINE}} install_extras: plotting wheel: false - extra-pip-args: ${{ env.extra }} - name: "Install ansys-grpc-dpf==0.4.0" shell: bash @@ -194,18 +195,22 @@ jobs: if: startsWith(github.head_ref, 'master') || github.event.action == 'ready_for_review' || !github.event.pull_request.draft uses: ./.github/workflows/examples.yml with: - ANSYS_VERSION: '241' + ANSYS_VERSION: "241" python_versions: '["3.10"]' - standalone_suffix: ${{ github.event.inputs.standalone_branch_suffix || '' }} + standalone_suffix: ${{ inputs.standalone_suffix || ''}} + custom-wheels: ${{ format('--find-links {0}', './dpf-standalone/v241/dist')}} + custom-requirements: requirements/requirements_dev.txt secrets: inherit docs: if: startsWith(github.head_ref, 'master') || github.event.action == 'ready_for_review' || !github.event.pull_request.draft uses: ./.github/workflows/docs.yml with: - ANSYS_VERSION: '241' - standalone_suffix: ${{ github.event.inputs.standalone_branch_suffix || '' }} + ANSYS_VERSION: "241" + standalone_suffix: ${{ inputs.standalone_suffix || ''}} event_name: ${{ github.event_name }} + custom-wheels: ./dpf-standalone/v241/dist + custom-requirements: requirements/requirements_dev.txt secrets: inherit upload-development-docs: diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index fd71b7295..8829856a6 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -108,6 +108,8 @@ jobs: wheelhouse: false wheel: false standalone_suffix: ${{ inputs.standalone_suffix }} + extra-pip-args: ${{ inputs.custom-wheels }} + custom-requirements: ${{ inputs.custom-requirements }} - name: "Prepare Testing Environment" uses: ansys/pydpf-actions/prepare_tests@v2.3 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2f6d1bf66..0baf68ea4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,6 +27,7 @@ repos: "docs src", "*.py *.rst *.md", ] + exclude: conf.py - repo: https://github.com/pycqa/pydocstyle rev: 6.3.0 diff --git a/requirements/requirements_dev.txt b/requirements/requirements_dev.txt new file mode 100644 index 000000000..c02b1467d --- /dev/null +++ b/requirements/requirements_dev.txt @@ -0,0 +1,3 @@ +ansys-dpf-gate==0.4.0.dev0 +ansys-dpf-gatebin==0.4.0.dev0 +ansys-grpc-dpf==0.8.0.dev0 From a6aebe9b31b8b41e5ec66b097346fff0ee9dc4d4 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Thu, 13 Jul 2023 11:36:47 +0200 Subject: [PATCH 89/99] Make test_named_selection.py retro-compatible --- tests/conftest.py | 4 ++++ tests/test_named_selection.py | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index a4371ed42..fdc571ff9 100755 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -186,6 +186,10 @@ def grpc_server(): server.shutdown() +SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0 = meets_version( + get_server_version(core._global_server()), "7.0" +) + SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_6_2 = meets_version( get_server_version(core._global_server()), "6.2" ) diff --git a/tests/test_named_selection.py b/tests/test_named_selection.py index bf016088a..b8485f65d 100644 --- a/tests/test_named_selection.py +++ b/tests/test_named_selection.py @@ -1,3 +1,4 @@ +import conftest import pytest from pytest import fixture @@ -53,7 +54,8 @@ def test_named_selection(): assert node_ns._scoping.location == dpf.locations.nodal element_ns = NamedSelection(name="test", element_ids=[0, 1]) assert element_ns._scoping.location == dpf.locations.elemental - face_ns = NamedSelection(name="test", face_ids=[0, 1]) - assert face_ns._scoping.location == dpf.locations.faces + if conftest.SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0: + face_ns = NamedSelection(name="test", face_ids=[0, 1]) + assert face_ns._scoping.location == dpf.locations.faces cell_ns = NamedSelection(name="test", cell_ids=[0, 1]) assert cell_ns._scoping.location == dpf.locations.elemental From 775433d5e6085a01b73b4a16e9a1e3906b574fa5 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Thu, 13 Jul 2023 16:05:14 +0200 Subject: [PATCH 90/99] Refactor nodes.py --- src/ansys/dpf/post/elements.py | 2 +- src/ansys/dpf/post/mesh.py | 6 +- src/ansys/dpf/post/nodes.py | 142 +++++++++++++++------------------ 3 files changed, 68 insertions(+), 82 deletions(-) diff --git a/src/ansys/dpf/post/elements.py b/src/ansys/dpf/post/elements.py index 70a09077e..f36067f53 100644 --- a/src/ansys/dpf/post/elements.py +++ b/src/ansys/dpf/post/elements.py @@ -231,7 +231,7 @@ def __getitem__(self, id: int) -> Element: # pylint: disable=redefined-builtin else: raise e # pragma: no cover - def __contains__(self, el: Element): + def __contains__(self, el: Element) -> bool: """Checks if the given element is in the list.""" return el.id in self._el_list.scoping.ids diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index 5216fc4f9..da68b0d54 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -16,7 +16,7 @@ from ansys.dpf.post.connectivity import ConnectivityListByIndex, ReturnMode from ansys.dpf.post.elements import Element, ElementListByIndex from ansys.dpf.post.named_selection import NamedSelections -from ansys.dpf.post.nodes import NodeListIdx +from ansys.dpf.post.nodes import NodeListByIndex from ansys.dpf.post.prop_fields_container import _PropertyFieldsContainer @@ -68,9 +68,9 @@ def get_element_by_id( return self.elements.by_id[id] @property - def nodes(self) -> NodeListIdx: + def nodes(self) -> NodeListByIndex: """Returns a list of nodes indexed by ID.""" - return NodeListIdx(self._meshed_region.nodes) + return NodeListByIndex(self._meshed_region.nodes) def get_node_by_id(self, id: int) -> Node: # pylint: disable=redefined-builtin """Returns a node of the mesh from a given ID.""" diff --git a/src/ansys/dpf/post/nodes.py b/src/ansys/dpf/post/nodes.py index 081b722db..d40ab5513 100644 --- a/src/ansys/dpf/post/nodes.py +++ b/src/ansys/dpf/post/nodes.py @@ -2,43 +2,39 @@ from __future__ import annotations -from collections.abc import Collection, Iterator +from abc import ABC, abstractmethod from typing import List -import ansys.dpf.core.nodes as nodes +from ansys.dpf.core import errors +import ansys.dpf.core.nodes as core_nodes class Node: """Wrapper class around dpf.core.nodes.Node.""" - def __init__(self, nodes: nodes.Nodes, index: int): + def __init__(self, node: core_nodes.Node, index: int): """Constructs a Node from its index and the original list.""" - self._nodes = nodes - self._index = index - - def _resolve(self) -> nodes.Node: - """Returns the original Node object in the original list.""" - return self._nodes[self._index] + self._node = node @property - def index(self) -> int: - """Returns the index of the node (zero-based).""" - return self._resolve().index + def coordinates(self) -> List[float]: + """Cartersian coordinates of the node.""" + return self._node.coordinates @property def id(self) -> int: """Returns the ID of the node.""" - return self._resolve().id + return self._node.id @property - def coordinates(self) -> List[float]: - """Cartersian coordinates of the node.""" - return self._resolve().coordinates + def index(self) -> int: + """Returns the index of the node (zero-based).""" + return self._node.index @property def to_element_connectivity(self) -> List[int]: """Elements indices connected to the node.""" - return self._resolve().nodal_connectivity + return self._node.nodal_connectivity def __str__(self) -> str: """Returns a string representation of the node.""" @@ -49,101 +45,91 @@ def __repr__(self) -> str: return f"Node(id={self.id})" -class NodeListIterator(Iterator): - """Iterator object for NodeListIdx.""" +class _NodeList(ABC): + """Iterator class for the NodeList.""" - def __init__(self, nodes: nodes.Nodes): - """Constructs an iterator from a Node List.""" + def __init__(self, nodes: core_nodes.Nodes): + """Constructs an iterator from a Nodes list.""" self._nodes = nodes self._idx = 0 def __next__(self) -> Node: """Returns the next Node in the list.""" - if self._idx >= self._nodes.__len__(): + if self._idx >= len(self._nodes): raise StopIteration - - ret = Node(self._nodes, self._idx) + ret = self[self._idx] self._idx += 1 return ret - def __iter__(self) -> NodeListIterator: - """Returns a new Iterator object.""" - return NodeListIterator(self._nodes) - - -class NodeListIdx(Collection): - """List of Node accessible by index.""" - - def __init__(self, nodes: nodes.Nodes): - """Constructs a NodeList from an existing dpf.core.nodes.Nodes object.""" - self._nodes = nodes - - def __getitem__(self, idx: int) -> Node: + def __getitem__(self, index: int) -> Node: """Returns a Node at a given index.""" - return Node(self._nodes, idx) - - def __contains__(self, node: nodes.Node) -> bool: - """Checks if given node is in the list.""" - return node.index >= 0 and node.index < self.__len__() - - def __iter__(self) -> NodeListIterator: - """Returns an iterator object on the list.""" - return NodeListIterator(self._nodes) + return Node(self._nodes[index]) def __len__(self) -> int: """Returns the number of nodes in the list.""" return self._nodes.n_nodes - @property - def by_id(self) -> NodeListById: - """Returns an equivalent list Accessible by ID.""" - return NodeListById(self._nodes) + def __repr__(self) -> str: + """Returns a string representation of a _NodeList object.""" + return f"{self.__class__.__name__}({self}, __len__={len(self)})" def _short_list(self) -> str: _str = "[" if self.__len__() > 3: - _fst = Node(self._nodes, 0) - _lst = Node(self._nodes, self.__len__() - 1) + _fst = Node(self._nodes[0]) + _lst = Node(self._nodes[len(self) - 1]) _str += f"{_fst}, ..., {_lst}" else: - el_list = [Node(self._nodes, idx) for idx in range(self.__len__())] - _str += ", ".join(map(repr, el_list)) + node_list = [Node(self._nodes[idx]) for idx in range(len(self))] + _str += ", ".join(map(repr, node_list)) _str += "]" return _str def __str__(self) -> str: - """Returns a string representation of NodeListIdx.""" + """Returns a string representation of a _NodeList object.""" return self._short_list() - def __repr__(self) -> str: - """Returns a string representation of NodeListIdx.""" - return f"NodeListIdx({self.__str__()}, __len__={self.__len__()})" + @abstractmethod + def __iter__(self): # pragma: no cover + """Returns an iterator object on the list.""" -class NodeListById(NodeListIdx): - """List of node accessible by ID.""" +class NodeListByIndex(_NodeList): + """Node List object using indexes as input.""" - def __init__(self, nodes: nodes.Nodes): - """Constructs a list from an existing core.nodes.Nodes object.""" - super().__init__(nodes) + @property + def by_id(self) -> NodeListById: + """Returns an equivalent list which accepts IDs as input.""" + return NodeListById(self._nodes) - def __getitem__(self, id: int) -> Node: # pylint: disable=redefined-builtin - """Returns a Node for a given ID.""" - idx = self._nodes.scoping.index(id) - return super().__getitem__(idx) + def __iter__(self) -> NodeListByIndex: + """Returns the object to iterate over.""" + self._idx = 0 + return self - def __contains__(self, node: nodes.Node) -> bool: + def __contains__(self, node: Node) -> bool: """Checks if the given node is in the list.""" - return node.id in self._nodes.scoping.ids + return len(self) > node.index >= 0 - def __iter__(self) -> NodeListIterator: - """Returns an iterator object on the list.""" - return super().__iter__() - def __len__(self) -> int: - """Returns the number of nodes in the list.""" - return self._nodes.n_nodes +class NodeListById(_NodeList): + """Node List object using IDs as input.""" - def __repr__(self) -> str: - """Returns a string representation of NodeListById.""" - return f"NodeListById({super().__str__()}, __len__={self.__len__()})" + def __getitem__(self, id: int) -> Node: # pylint: disable=redefined-builtin + """Access a Node with an ID.""" + idx = self._nodes.scoping.index(id) + try: + return super().__getitem__(idx) + except errors.DPFServerException as e: + if "node not found" in str(e): + raise ValueError(f"Node with ID={id} not found in the list.") + else: + raise e # pragma: no cover + + def __contains__(self, node: Node) -> bool: + """Checks if the given node is in the list.""" + return node.id in self._nodes.scoping.ids + + def __iter__(self) -> NodeListByIndex: + """Returns the object to iterate over.""" + return NodeListByIndex(self._nodes) From 3dab0c57e1014d4618dbe8977602503d8eb455dc Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Thu, 13 Jul 2023 16:24:54 +0200 Subject: [PATCH 91/99] Coverage nodes.py --- src/ansys/dpf/post/nodes.py | 2 +- tests/test_nodes.py | 58 +++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/test_nodes.py diff --git a/src/ansys/dpf/post/nodes.py b/src/ansys/dpf/post/nodes.py index d40ab5513..21d867ca6 100644 --- a/src/ansys/dpf/post/nodes.py +++ b/src/ansys/dpf/post/nodes.py @@ -12,7 +12,7 @@ class Node: """Wrapper class around dpf.core.nodes.Node.""" - def __init__(self, node: core_nodes.Node, index: int): + def __init__(self, node: core_nodes.Node): """Constructs a Node from its index and the original list.""" self._node = node diff --git a/tests/test_nodes.py b/tests/test_nodes.py new file mode 100644 index 000000000..6bb122af5 --- /dev/null +++ b/tests/test_nodes.py @@ -0,0 +1,58 @@ +import pytest + +from ansys.dpf import core as dpf +from ansys.dpf.post import examples +from ansys.dpf.post.nodes import Node, NodeListById, NodeListByIndex + + +def test_node(): + model = dpf.Model(examples.find_static_rst()) + core_nodes = model.metadata.meshed_region.nodes + node = Node(node=core_nodes[0]) + ref = [0.015, 0.045, 0.015] + assert node.coordinates == ref + assert node.id == 1 + assert node.index == 0 + ref = [0, 1, 2, 3, 4, 5, 6, 7] + assert list(node.to_element_connectivity) == ref + ref = "Node(id=1, coordinates=[0.015, 0.045, 0.015])" + assert str(node) == ref + ref = "Node(id=1)" + assert repr(node) == ref + + +def test_nodes_nodes_list_by_idx(): + model = dpf.Model(examples.find_static_rst()) + core_nodes = model.metadata.meshed_region.nodes + nodes_list_by_index = NodeListByIndex(nodes=core_nodes) + for i in nodes_list_by_index: + assert isinstance(i, Node) + for i in nodes_list_by_index: + assert isinstance(i, Node) + assert nodes_list_by_index[1].id == 2 + assert len(nodes_list_by_index) == 81 + ref = ( + "NodeListByIndex([Node(id=1, coordinates=[0.015, 0.045, 0.015]), ..., " + "Node(id=81, coordinates=[0.03, 0.045, 0.0075])], __len__=81)" + ) + assert repr(nodes_list_by_index) == ref + ref = ( + "[Node(id=1, coordinates=[0.015, 0.045, 0.015]), ..., " + "Node(id=81, coordinates=[0.03, 0.045, 0.0075])]" + ) + assert str(nodes_list_by_index) == ref + assert nodes_list_by_index[0] in nodes_list_by_index + nodes_list_by_id = nodes_list_by_index.by_id + assert isinstance(nodes_list_by_id, NodeListById) + + +def test_nodes_nodes_list_by_id(): + model = dpf.Model(examples.find_static_rst()) + core_nodes = model.metadata.meshed_region.nodes + nodes_list_by_id = NodeListById(nodes=core_nodes) + for i in nodes_list_by_id: + assert isinstance(i, Node) + assert isinstance(nodes_list_by_id[5], Node) + assert nodes_list_by_id[5] in nodes_list_by_id + with pytest.raises(ValueError, match="not found"): + _ = nodes_list_by_id[0] From d0bffb3c58d73f89dfc74da4b0b4a805aa305ff0 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 17 Jul 2023 11:47:34 +0200 Subject: [PATCH 92/99] Move PropertyFieldsContainer to core --- src/ansys/dpf/post/dataframe.py | 10 +- src/ansys/dpf/post/mesh.py | 6 +- src/ansys/dpf/post/prop_fields_container.py | 312 -------------------- 3 files changed, 8 insertions(+), 320 deletions(-) delete mode 100644 src/ansys/dpf/post/prop_fields_container.py diff --git a/src/ansys/dpf/post/dataframe.py b/src/ansys/dpf/post/dataframe.py index 716136cf6..550463187 100644 --- a/src/ansys/dpf/post/dataframe.py +++ b/src/ansys/dpf/post/dataframe.py @@ -14,6 +14,7 @@ import ansys.dpf.core as dpf from ansys.dpf.core.dpf_array import DPFArray from ansys.dpf.core.plotter import DpfPlotter +from ansys.dpf.core.property_fields_container import PropertyFieldsContainer import ansys.dpf.gate.errors import numpy as np @@ -29,7 +30,6 @@ SetIndex, ref_labels, ) -from ansys.dpf.post.prop_fields_container import _PropertyFieldsContainer default_display_max_columns = 6 default_display_max_rows = 6 @@ -40,7 +40,7 @@ class DataFrame: def __init__( self, - data: Union[dpf.FieldsContainer, _PropertyFieldsContainer], + data: Union[dpf.FieldsContainer, PropertyFieldsContainer], index: Union[MultiIndex, Index, List[int]], columns: Union[MultiIndex, Index, List[str], None] = None, ): @@ -57,7 +57,7 @@ def __init__( """ self._index = index if isinstance(data, dpf.FieldsContainer) or isinstance( - data, _PropertyFieldsContainer + data, PropertyFieldsContainer ): self._fc = data # if index is None: @@ -316,7 +316,7 @@ def select(self, **kwargs) -> DataFrame: f"Selection on a DataFrame with index " f"'{mesh_index_name}' is not yet supported" ) - if isinstance(input_fc, _PropertyFieldsContainer): + if isinstance(input_fc, PropertyFieldsContainer): fc = input_fc.rescope(mesh_scoping) else: rescope_fc = dpf.operators.scoping.rescope_fc( @@ -355,7 +355,7 @@ def select(self, **kwargs) -> DataFrame: results_index, set_index, ] - if isinstance(fc, _PropertyFieldsContainer): + if isinstance(fc, PropertyFieldsContainer): column_indexes = [results_index] label_indexes = [] diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index da68b0d54..33fa0b8bb 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -10,6 +10,7 @@ import ansys.dpf.core as dpf from ansys.dpf.core.nodes import Node +from ansys.dpf.core.property_fields_container import PropertyFieldsContainer import ansys.dpf.post as post from ansys.dpf.post import index, locations @@ -17,7 +18,6 @@ from ansys.dpf.post.elements import Element, ElementListByIndex from ansys.dpf.post.named_selection import NamedSelections from ansys.dpf.post.nodes import NodeListByIndex -from ansys.dpf.post.prop_fields_container import _PropertyFieldsContainer class Mesh: @@ -80,7 +80,7 @@ def get_node_by_id(self, id: int) -> Node: # pylint: disable=redefined-builtin def element_types(self) -> post.DataFrame: """Returns a DataFrame containing element types ID.""" label = "elem_type_id" - fields_container = _PropertyFieldsContainer() + fields_container = PropertyFieldsContainer() field = self._meshed_region.elements.element_types_field fields_container.add_field(label_space={}, field=field) @@ -102,7 +102,7 @@ def element_types(self) -> post.DataFrame: def materials(self) -> post.DataFrame: """Returns a DataFrame containing element materials ID.""" label = "material_id" - fields_container = _PropertyFieldsContainer() + fields_container = PropertyFieldsContainer() field = self._meshed_region.elements.materials_field fields_container.add_field(label_space={}, field=field) diff --git a/src/ansys/dpf/post/prop_fields_container.py b/src/ansys/dpf/post/prop_fields_container.py deleted file mode 100644 index 8492254d3..000000000 --- a/src/ansys/dpf/post/prop_fields_container.py +++ /dev/null @@ -1,312 +0,0 @@ -"""Module containing a minimal implementation of PropertyFieldContainer on pydpf-post side.""" - -from __future__ import annotations - -from collections.abc import Sequence -import copy -from typing import Dict - -import ansys.dpf.core as dpf -from ansys.dpf.core.property_field import PropertyField -import numpy as np - - -class _LabelSpaceKV: - """Class for internal use to associate a label space with a field.""" - - def __init__(self, _dict: Dict[str, int], _field): - """Constructs an association between a dictionary and a field.""" - self._dict = _dict - self._field = _field - - @property - def dict(self): - """Returns the associated dictionary.""" - return self._dict - - @property - def field(self): - """Returns the associated field.""" - return self._field - - @field.setter - def field(self, value): - self._field = value - - def __str__(self): - """Returns a string representationf of the association.""" - field_str = str(self._field).replace("\n", " ") - return f"Label Space: {self._dict} with field {field_str}" - - -class _PropertyFieldsContainer(Sequence): - """Minimal implementation of a FieldsContainer specialized for PropertyFieldsContainer.""" - - def __init__(self, fields_container=None, server=None): - """Constructs an empty PropertyFieldsContainer or from a PropertyFieldsContainer.""" - # default constructor - self._labels = [] # used by Dataframe - self.scopings = [] - self._server = None # used by Dataframe - - self.label_spaces = [] - self.ids = [] - - # PropertyFieldsContainer copy - if fields_container is not None: - self._labels = copy.deepcopy(fields_container.labels) - # self.scopings = copy.deepcopy(fields_container.scopings) - self._server = fields_container._server - - # self.ids = copy.deepcopy(fields_container.ids) - - for ls in fields_container.label_spaces: - self.add_entry(copy.deepcopy(ls.dict), ls.field.as_local_field()) - - # server copy - if server is not None: - self._server = server - - # Collection - def __str__(self): - """Returns a string representation of a PropertyFieldsContainer.""" - txt = f"DPF PropertyFieldsContainer with {len(self)} fields\n" - for idx, ls in enumerate(self.label_spaces): - txt += f"\t {idx}: {ls}\n" - - return txt - - @property - def labels(self): - """Returns all labels of the PropertyFieldsContainer.""" - return self._labels - - @labels.setter - def labels(self, vals): - self.set_labels(vals) - - def set_labels(self, labels): - """Sets all the label of the PropertyFieldsContainer.""" - if len(self._labels) != 0: - raise ValueError("labels already set") - - for l in labels: - self.add_label(l) - - def add_label(self, label): - """Adds a label.""" - if label not in self._labels: - self._labels.append(label) - self.scopings.append([]) - - def has_label(self, label): - """Check if a PorpertyFieldsContainer contains a given label.""" - return label in self.labels - - # used by Dataframe - def get_label_space(self, idx): - """Get a Label Space at a given index.""" - return self.label_spaces[idx]._dict - - # used by Dataframe - def get_label_scoping(self, label="time"): - """Returns a scoping on the fields concerned by the given label.""" - if label in self.labels: - scoping_ids = self.scopings[self.labels.index(label)] - return dpf.Scoping(ids=scoping_ids, location="") - raise KeyError("label {label} not found") - - def add_entry(self, label_space: Dict[str, int], value): - """Adds a PropertyField associated with a dictionary.""" - new_id = self._new_id() - - if hasattr(value, "_server"): - self._server = value._server - - # add Label Space - self.label_spaces.append(_LabelSpaceKV(label_space, value)) - - # Update IDs - self.ids.append(new_id) - - # Update Scopings - for label in label_space.keys(): - label_idx = self.labels.index(label) - self.scopings[label_idx].append(new_id) - - def get_entries(self, label_space_or_index): - """Returns a list of fields from a complete or partial specification of a dictionary.""" - if isinstance(label_space_or_index, int): - idx: int = label_space_or_index - return [self.label_spaces[idx].field] - else: - _dict: Dict[str, int] = label_space_or_index - are_keys_in_labels = [key in self.labels for key in _dict.keys()] - if all(are_keys_in_labels): - remaining = set(range(len(self.label_spaces))) - for key in _dict.keys(): - val = _dict[key] - to_remove = set() - for idx in remaining: - ls = self.label_spaces[idx] - if ls.dict[key] != val: - to_remove.add(idx) - remaining = remaining.difference(to_remove) - - idx_to_field = lambda idx: self.label_spaces[idx].field - return list(map(idx_to_field, remaining)) - else: - bad_idx = are_keys_in_labels.index(False) - bad_key = _dict.keys()[bad_idx] - raise KeyError(f"Key {bad_key} is not in labels: {self.labels}") - - def get_entry(self, label_space_or_index): - """Returns the field or (first field found) corresponding to the given dictionary.""" - ret = self.get_entries(label_space_or_index) - - if len(ret) != 0: - return ret[0] - - raise IndexError("Could not find corresponding entry") - - def _new_id(self): - """Helper method generating a new id when calling add_entry(...).""" - if len(self.ids) == 0: - self.last_id = 1 - return self.last_id - else: - self.last_id += 1 - return self.last_id - - # FieldsContainer - def create_subtype(self, obj_by_copy): - """Instantiate a PropertyField with given instance, using the server of the container.""" - return PropertyField(property_field=obj_by_copy, server=self._server) - - def get_fields_by_time_complex_ids(self, timeid=None, complexid=None): - """Returns fields at a requested time or complex ID.""" - label_space = {"time": timeid, "complex": complexid} - return self.get_fields(label_space) - - def get_field_by_time_complex_ids(self, timeid=None, complexid=None): - """Returns field at a requested time or complex ID.""" - label_space = {"time": timeid, "complex": complexid} - return self.get_field(label_space) - - def __time_complex_label_space__(self, timeid=None, complexid=None): - """Not implemented.""" - raise NotImplementedError - - # used by Dataframe - def get_fields(self, label_space): - """Returns the list of fields associated with given label space.""" - return self.get_entries(label_space) - - def get_field(self, label_space_or_index): - """Retrieves the field at a requested index or label space.""" - return self.get_entry(label_space_or_index) - - def get_field_by_time_id(self, timeid=None): - """Retrieves the complex field at a requested timeid.""" - label_space = {"time": timeid} - if self.has_label("complex"): - label_space["complex"] = 0 - return self.get_field(label_space) - - def get_imaginary_fields(self, timeid=None): - """Retrieve the complex fields at a requested timeid.""" - label_space = {"time": timeid, "complex": 1} - return self.get_fields(label_space) - - def get_imaginary_field(self, timeid=None): - """Retrieve the complex field at a requested time.""" - label_space = {"time": timeid, "complex": 1} - return self.get_field(label_space) - - # used by Dataframe - def __getitem__(self, key): - """Retrieve the field at a requested index.""" - return self.get_field(key) - - def __len__(self): - """Retrieve the number of label spaces.""" - return len(self.label_spaces) - - def add_field(self, label_space, field): - """Add or update a field at a requested label space.""" - self.add_entry(label_space, field) - - def add_field_by_time_id(self, field, timeid=1): - """Add or update a field at a requested timeid.""" - if not self.has_label("time"): - self.add_label("time") - - label_space = {"time": timeid} - - if self.has_label("complex"): - label_space["complex"] = 0 - - self.add_field(label_space, field) - - def add_imaginary_field(self, field, timeid=1): - """Add or update an imaginary field at a requested timeid.""" - if not self.has_label("time"): - self.add_label("time") - if not self.has_label("complex"): - self.add_label("complex") - - label_space = {"time": timeid, "complex": 1} - self.add_field(label_space, field) - - def select_component(self, index): - """Not implemented.""" - raise NotImplementedError - - @property - def time_freq_support(self): - """Not implemented.""" - raise NotImplementedError - - @time_freq_support.setter - def time_freq_support(self, value): - """Not implemented.""" - raise NotImplementedError - - def deep_copy(self, server=None): - """Not implemented.""" - raise NotImplementedError - - def get_time_scoping(self): - """Retrieves the time scoping containing the time sets.""" - return self.get_label_scoping("time") - - def animate(self, save_as=None, deform_by=None, scale_factor=1.0, **kwargs): - """Not implemented.""" - raise NotImplementedError - - def __sub__(self, fields_b): - """Not implemented.""" - raise NotImplementedError - - def __pow__(self, value): - """Not implemented.""" - raise NotImplementedError - - def __mul__(self, value): - """Not implemented.""" - raise NotImplementedError - - def _set_field(self, ls_idx, field): - self.label_spaces[ls_idx].field = field - - def rescope(self, scoping: dpf.Scoping): - """Helper function to reproduce functionality of rescope_fc Operator.""" - copy_fc = _PropertyFieldsContainer(self, server=None) - for idx, label_space in enumerate(copy_fc.label_spaces): - pfield = PropertyField(location=label_space.field.location) - pfield.data = np.ravel( - [label_space._field.get_entity_data_by_id(id) for id in scoping.ids] - ) - pfield.scoping.ids = scoping.ids - copy_fc._set_field(idx, pfield) - return copy_fc From 747b8894e01a4c42789c64a615c32be752ff053f Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Mon, 17 Jul 2023 11:48:38 +0200 Subject: [PATCH 93/99] Remove rogue prints from tests --- tests/test_mesh.py | 4 ++-- tests/test_meshes.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_mesh.py b/tests/test_mesh.py index 57c93f10f..7a1f431b1 100644 --- a/tests/test_mesh.py +++ b/tests/test_mesh.py @@ -108,7 +108,7 @@ def test_mesh_str(mesh): def test_mesh_coordinates(mesh): coord = mesh.coordinates - print(coord) + # print(coord) ref = """ results coord (m) node_ids components @@ -148,7 +148,7 @@ def test_mesh_materials(mesh): def test_mesh_element_types(mesh): element_types = mesh.element_types - print(element_types) + # print(element_types) ref = """ results elem_type_id element_ids diff --git a/tests/test_meshes.py b/tests/test_meshes.py index 08d45a0df..0baf31f33 100644 --- a/tests/test_meshes.py +++ b/tests/test_meshes.py @@ -54,6 +54,6 @@ def test_meshes_select(meshes): assert isinstance(meshes_mat, post.Mesh) assert len(meshes_mat.node_ids) == 248 meshes_mat = meshes.select(mat=1) - print(meshes_mat._core_object) + # print(meshes_mat._core_object) assert isinstance(meshes_mat, post.Meshes) assert len(meshes_mat) == 2 From 7536af01af3885cd7e1b981d3181452d714d0b0a Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 18 Jul 2023 09:25:35 +0200 Subject: [PATCH 94/99] Finalize move of PropertyFieldsContainer to ansys-dpf-core --- src/ansys/dpf/post/dataframe.py | 4 +++- src/ansys/dpf/post/mesh.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ansys/dpf/post/dataframe.py b/src/ansys/dpf/post/dataframe.py index 550463187..2b506ca65 100644 --- a/src/ansys/dpf/post/dataframe.py +++ b/src/ansys/dpf/post/dataframe.py @@ -14,7 +14,9 @@ import ansys.dpf.core as dpf from ansys.dpf.core.dpf_array import DPFArray from ansys.dpf.core.plotter import DpfPlotter -from ansys.dpf.core.property_fields_container import PropertyFieldsContainer +from ansys.dpf.core.property_fields_container import ( + _MockPropertyFieldsContainer as PropertyFieldsContainer, +) import ansys.dpf.gate.errors import numpy as np diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index 33fa0b8bb..a4fee55ad 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -10,7 +10,9 @@ import ansys.dpf.core as dpf from ansys.dpf.core.nodes import Node -from ansys.dpf.core.property_fields_container import PropertyFieldsContainer +from ansys.dpf.core.property_fields_container import ( + _MockPropertyFieldsContainer as PropertyFieldsContainer, +) import ansys.dpf.post as post from ansys.dpf.post import index, locations From 9b215bf9eb1e1518540535aac4dcd6eeb0f9267b Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 18 Jul 2023 14:22:33 +0200 Subject: [PATCH 95/99] Add examples to docstrings --- src/ansys/dpf/post/mesh.py | 256 +++++++++++++++++++++++--- src/ansys/dpf/post/named_selection.py | 7 + 2 files changed, 237 insertions(+), 26 deletions(-) diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index a4fee55ad..f22b2a10f 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -35,52 +35,158 @@ def __str__(self): @property def named_selections(self) -> NamedSelections: - """Returns a dictionary of available named selections for this mesh.""" + """Returns a dictionary of available named selections for this mesh. + + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post import examples + >>> simulation = post.load_simulation(examples.static_rst) + >>> print(simulation.mesh.named_selections) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + NamedSelections dictionary with 1 named selections: + - '_FIXEDSU' + """ return NamedSelections(self) @property def node_ids(self) -> List[int]: - """Returns the list of node IDs in the mesh.""" + """Returns the list of node IDs in the mesh. + + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post import examples + >>> simulation = post.load_simulation(examples.static_rst) + >>> print(simulation.mesh.node_ids) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 + 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 + 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 + 73 74 75 76 77 78 79 80 81] + """ return self._meshed_region.nodes.scoping.ids @property def num_nodes(self) -> int: - """Returns the number of nodes in the mesh.""" + """Returns the number of nodes in the mesh. + + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post import examples + >>> simulation = post.load_simulation(examples.static_rst) + >>> print(simulation.mesh.num_nodes) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + 81 + """ return len(self.node_ids) @property def element_ids(self) -> List[int]: - """Returns the list of element IDs in the mesh.""" + """Returns the list of element IDs in the mesh. + + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post import examples + >>> simulation = post.load_simulation(examples.static_rst) + >>> print(simulation.mesh.element_ids) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + [5 6 1 2 7 8 3 4] + """ return self._meshed_region.elements.scoping.ids @property def num_elements(self) -> int: - """Returns the number of element in the mesh.""" + """Returns the number of element in the mesh. + + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post import examples + >>> simulation = post.load_simulation(examples.static_rst) + >>> print(simulation.mesh.num_elements) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + 8 + """ return len(self.element_ids) @property def elements(self) -> ElementListByIndex: - """Returns a list of elements indexed by ID.""" + """Returns a list of elements indexed by ID. + + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post import examples + >>> simulation = post.load_simulation(examples.static_rst) + >>> print(simulation.mesh.elements) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + [hex20, ..., hex20] + """ return ElementListByIndex(self._meshed_region.elements) def get_element_by_id( self, id: int # pylint: disable=redefined-builtin ) -> Element: - """Returns an element in the mesh from a given ID.""" + """Returns an element in the mesh from a given ID. + + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post import examples + >>> simulation = post.load_simulation(examples.static_rst) + >>> print(simulation.mesh.get_element_by_id(1)) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + DPF Element 1 + Index: 2 + Nodes: 20 + Type: Hex20 + Shape: Solid + """ return self.elements.by_id[id] @property def nodes(self) -> NodeListByIndex: - """Returns a list of nodes indexed by ID.""" + """Returns a list of nodes indexed by ID. + + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post import examples + >>> simulation = post.load_simulation(examples.static_rst) + >>> print(simulation.mesh.nodes) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + [Node(id=1, coordinates=[0.015, 0.045, 0.015]), ..., Node(id=81, coordinates=[0.03, 0.045, 0.0075])] + """ # noqa return NodeListByIndex(self._meshed_region.nodes) def get_node_by_id(self, id: int) -> Node: # pylint: disable=redefined-builtin - """Returns a node of the mesh from a given ID.""" + """Returns a node of the mesh from a given ID. + + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post import examples + >>> simulation = post.load_simulation(examples.static_rst) + >>> print(simulation.mesh.get_node_by_id(1)) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + Node(id=1, coordinates=[0.015, 0.045, 0.015]) + """ return self.nodes.by_id[id] @property def element_types(self) -> post.DataFrame: - """Returns a DataFrame containing element types ID.""" + """Returns a DataFrame containing element types ID. + + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post import examples + >>> simulation = post.load_simulation(examples.static_rst) + >>> print(simulation.mesh.element_types) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + results elem_type_id + element_ids + 5 1 + 6 1 + 1 1 + 2 1 + 7 1 + 8 1 + ... ... + """ label = "elem_type_id" fields_container = PropertyFieldsContainer() field = self._meshed_region.elements.element_types_field @@ -102,7 +208,24 @@ def element_types(self) -> post.DataFrame: @property def materials(self) -> post.DataFrame: - """Returns a DataFrame containing element materials ID.""" + """Returns a DataFrame containing element materials ID. + + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post import examples + >>> simulation = post.load_simulation(examples.static_rst) + >>> print(simulation.mesh.materials) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + results material_id + element_ids + 5 1 + 6 1 + 1 1 + 2 1 + 7 1 + 8 1 + ... ... + """ label = "material_id" fields_container = PropertyFieldsContainer() field = self._meshed_region.elements.materials_field @@ -124,7 +247,22 @@ def materials(self) -> post.DataFrame: @property def element_to_node_ids_connectivity(self): - """Returns a.""" + """Returns a connectivity map between element index and node IDs. + + To get the connectivity map by element ID, use the 'by_id' property of the object returned. + + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post import examples + >>> simulation = post.load_simulation(examples.static_rst) + >>> element_to_node_ids_connectivity = simulation.mesh.element_to_node_ids_connectivity + >>> print(element_to_node_ids_connectivity[0]) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + [1, 26, 14, 12, 2, 27, 15, 13, 33, 64, 59, 30, 37, 65, 61, 34, 28, 81, 63, 58] + >>> element_to_node_ids_connectivity_by_id = element_to_node_ids_connectivity.by_id + >>> print(element_to_node_ids_connectivity[1]) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + [1, 12, 14, 26, 3, 10, 9, 4, 30, 59, 64, 33, 41, 53, 43, 38, 29, 56, 54, 44] + """ conn_field = self._meshed_region.elements.connectivities_field nodes_scoping = self._meshed_region.nodes.scoping return ConnectivityListByIndex( @@ -133,7 +271,22 @@ def element_to_node_ids_connectivity(self): @property def node_to_element_ids_connectivity(self): - """Returns a list of Element ID for a given Node index.""" + """Returns a connectivity map between node index and element IDs. + + To get the connectivity map by node ID, use the 'by_id' property of the object returned. + + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post import examples + >>> simulation = post.load_simulation(examples.static_rst) + >>> node_to_element_ids_connectivity = simulation.mesh.node_to_element_ids_connectivity + >>> print(node_to_element_ids_connectivity[0]) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + [5, 6, 1, 2, 7, 8, 3, 4] + >>> node_to_element_ids_connectivity_by_id = node_to_element_ids_connectivity.by_id + >>> print(node_to_element_ids_connectivity_by_id[1]) # doctest: +NORMALIZE_WHITESPACE + [5, 6, 1, 2, 7, 8, 3, 4] + """ conn_field = self._meshed_region.nodes.nodal_connectivity_field elems_scoping = self._meshed_region.elements.scoping return ConnectivityListByIndex( @@ -142,7 +295,22 @@ def node_to_element_ids_connectivity(self): @property def element_to_node_connectivity(self): - """Returns a list of Node index for a given Element index.""" + """Returns a connectivity map between element index and node indexes. + + To get the connectivity map by element ID, use the 'by_id' property of the object returned. + + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post import examples + >>> simulation = post.load_simulation(examples.static_rst) + >>> element_to_node_connectivity = simulation.mesh.element_to_node_connectivity + >>> print(element_to_node_connectivity[0]) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + [0, 25, 13, 11, 1, 26, 14, 12, 32, 63, 58, 29, 36, 64, 60, 33, 27, 80, 62, 57] + >>> element_to_node_connectivity_by_id = element_to_node_connectivity.by_id + >>> print(element_to_node_connectivity_by_id[1]) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + [0, 17, 19, 25, 1, 18, 20, 26, 30, 69, 74, 32, 34, 71, 75, 36, 27, 68, 73, 80] + """ conn_field = self._meshed_region.elements.connectivities_field nodes_scoping = self._meshed_region.nodes.scoping return ConnectivityListByIndex( @@ -151,7 +319,22 @@ def element_to_node_connectivity(self): @property def node_to_element_connectivity(self): - """Returns a list of Element index for a given Node index.""" + """Returns a connectivity map between node index and element indexes. + + To get the connectivity map by node ID, use the 'by_id' property of the object returned. + + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post import examples + >>> simulation = post.load_simulation(examples.static_rst) + >>> node_to_element_connectivity = simulation.mesh.node_to_element_connectivity + >>> print(node_to_element_connectivity[0]) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + [0, 1, 2, 3, 4, 5, 6, 7] + >>> node_to_element_connectivity_by_id = node_to_element_connectivity.by_id + >>> print(node_to_element_connectivity_by_id[1]) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + [0, 1, 2, 3, 4, 5, 6, 7] + """ conn_field = self._meshed_region.nodes.nodal_connectivity_field elems_scoping = self._meshed_region.elements.scoping return ConnectivityListByIndex( @@ -160,11 +343,33 @@ def node_to_element_connectivity(self): @property def unit(self) -> str: - """Returns the unit of the mesh (same as coordinates of the mesh).""" + """Returns the unit of the mesh (same as coordinates of the mesh). + + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post import examples + >>> simulation = post.load_simulation(examples.static_rst) + >>> print(simulation.mesh.unit) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + m + """ return self._meshed_region.unit @unit.setter def unit(self, value: str): + """Set the unit of the mesh. + + Examples + -------- + >>> from ansys.dpf import post + >>> from ansys.dpf.post import examples + >>> simulation = post.load_simulation(examples.static_rst) + >>> print(simulation.mesh.unit) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + m + >>> simulation.mesh.unit = "mm" + >>> print(simulation.mesh.unit) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + mm + """ self._meshed_region.unit = value @property @@ -210,16 +415,15 @@ def coordinates(self) -> post.DataFrame: >>> mesh = simulation.mesh >>> coord = mesh.coordinates >>> print(coord) # doctest: +NORMALIZE_WHITESPACE - results coord (m) - node_ids components - 1 X 1.5000e-02 - Y 4.5000e-02 - Z 1.5000e-02 - 2 X 1.5000e-02 - Y 4.5000e-02 - Z 0.0000e+00 - ... - + results coord (m) + node_ids components + 1 X 1.5000e-02 + Y 4.5000e-02 + Z 1.5000e-02 + 2 X 1.5000e-02 + Y 4.5000e-02 + Z 0.0000e+00 + ... ... ... """ from ansys.dpf.post.simulation import vector_component_names diff --git a/src/ansys/dpf/post/named_selection.py b/src/ansys/dpf/post/named_selection.py index e63e9e0ef..0c97f1b45 100644 --- a/src/ansys/dpf/post/named_selection.py +++ b/src/ansys/dpf/post/named_selection.py @@ -50,6 +50,13 @@ def __next__(self) -> str: self._idx += 1 return res + def __str__(self) -> str: + """String representation.""" + txt = f"NamedSelections dictionary with {len(self)} named selections:" + for ns in self: + txt += f"\n\t- '{ns}'" + return txt + class NamedSelection: """Named Selection class associating a name to a list of mesh entities.""" From 83e35afe6f4b3ef9fd389604ce170a0fe40ecc31 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Tue, 18 Jul 2023 18:17:09 +0200 Subject: [PATCH 96/99] Take remarks into account --- src/ansys/dpf/post/connectivity.py | 4 ++-- src/ansys/dpf/post/elements.py | 2 +- src/ansys/dpf/post/mesh.py | 4 ++-- src/ansys/dpf/post/named_selection.py | 13 ------------- src/ansys/dpf/post/simulation.py | 9 +-------- tests/test_elements.py | 2 +- tests/test_named_selection.py | 4 ---- 7 files changed, 7 insertions(+), 31 deletions(-) diff --git a/src/ansys/dpf/post/connectivity.py b/src/ansys/dpf/post/connectivity.py index 06a9adab7..981b62bb8 100644 --- a/src/ansys/dpf/post/connectivity.py +++ b/src/ansys/dpf/post/connectivity.py @@ -28,8 +28,8 @@ def __init__( ): """Constructs a _ConnectivityList by wrapping the given PropertyField. - Arguments: - --------- + Parameters + ---------- field: Field of connectivity. mode: diff --git a/src/ansys/dpf/post/elements.py b/src/ansys/dpf/post/elements.py index f36067f53..de1357174 100644 --- a/src/ansys/dpf/post/elements.py +++ b/src/ansys/dpf/post/elements.py @@ -24,7 +24,7 @@ def __init__(self, arg: Union[dpf.ElementDescriptor, int]): raise TypeError("Given argument is not an int nor an ElementDescriptor") @property - def enum_id(self) -> dpf.element_types: + def elem_type_id(self) -> dpf.element_types: """Element type in the element_types enum.""" return self._el_desc.enum_id diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index f22b2a10f..7353b7ab8 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -77,7 +77,7 @@ def num_nodes(self) -> int: >>> print(simulation.mesh.num_nodes) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS 81 """ - return len(self.node_ids) + return self._meshed_region.nodes.n_nodes @property def element_ids(self) -> List[int]: @@ -105,7 +105,7 @@ def num_elements(self) -> int: >>> print(simulation.mesh.num_elements) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS 8 """ - return len(self.element_ids) + return self._meshed_region.elements.n_elements @property def elements(self) -> ElementListByIndex: diff --git a/src/ansys/dpf/post/named_selection.py b/src/ansys/dpf/post/named_selection.py index 0c97f1b45..fb47098c3 100644 --- a/src/ansys/dpf/post/named_selection.py +++ b/src/ansys/dpf/post/named_selection.py @@ -3,7 +3,6 @@ from __future__ import annotations from collections.abc import Mapping -import copy from typing import TYPE_CHECKING, List, Union import ansys.dpf.core as dpf @@ -138,18 +137,6 @@ def size(self) -> int: """Length of the list of IDs.""" return self._scoping.size - def deep_copy(self, server=None) -> NamedSelection: - """Create a deep copy of the underlying scoping's data on a given server.""" - new_scoping = self._scoping.deep_copy(server) - new_name = copy.copy(self._name) - return NamedSelection(name=new_name, scoping=new_scoping) - - def as_local_scoping(self) -> NamedSelection: - """Create a deep copy of the underlying scoping that can be modified locally.""" - local_scoping = self._scoping.as_local_scoping() - local_name = copy.copy(self._name) - return NamedSelection(name=local_name, scoping=local_scoping) - def __repr__(self) -> str: """Pretty print string of the NamedSelection.""" return f"NamedSelection '{self.name}'\n with {self._scoping.__str__()}" diff --git a/src/ansys/dpf/post/simulation.py b/src/ansys/dpf/post/simulation.py index 5be3c1139..262b8e64b 100644 --- a/src/ansys/dpf/post/simulation.py +++ b/src/ansys/dpf/post/simulation.py @@ -93,14 +93,7 @@ def release_streams(self): @property def mesh_info(self): """Return available mesh information.""" - mesh_selection_manager_provider_op = ( - dpf.operators.metadata.mesh_selection_manager_provider( - data_sources=self._data_sources - ) - ) - outputs = mesh_selection_manager_provider_op.outputs - # print(dir(outputs)) - return outputs.mesh_selection_manager + return self._model.metadata.mesh_info @property def results(self) -> List[dpf.result_info.available_result.AvailableResult]: diff --git a/tests/test_elements.py b/tests/test_elements.py index cc5014429..265ce9c70 100644 --- a/tests/test_elements.py +++ b/tests/test_elements.py @@ -38,7 +38,7 @@ def test_element_type(): assert repr(el_type) == ref ref = "Quadratic 20-nodes Hexa" assert el_type.description == ref - assert el_type.enum_id == dpf.element_types.Hex20 + assert el_type.elem_type_id == dpf.element_types.Hex20 assert el_type.name == "hex20" assert el_type.shape == "solid" assert el_type.num_corner_nodes == 8 diff --git a/tests/test_named_selection.py b/tests/test_named_selection.py index b8485f65d..3db6b5eaf 100644 --- a/tests/test_named_selection.py +++ b/tests/test_named_selection.py @@ -41,10 +41,6 @@ def test_named_selection(): """ # noqa assert repr(ns) == ref - assert ns.deep_copy() == ns - - assert ns.as_local_scoping() == ns - with pytest.raises(ValueError, match="No list of entity IDs given."): _ = NamedSelection(name="test") with pytest.raises(ValueError, match="NamedSelection accepts only"): From 7d132e74248538da46a891fa6c0b88e51a9c4972 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Thu, 20 Jul 2023 19:16:55 +0200 Subject: [PATCH 97/99] Throw when trying to instantiate a post.Mesh with a None as MeshedRegion --- src/ansys/dpf/post/mesh.py | 2 ++ tests/test_mesh.py | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ansys/dpf/post/mesh.py b/src/ansys/dpf/post/mesh.py index 7353b7ab8..4e53a2d92 100644 --- a/src/ansys/dpf/post/mesh.py +++ b/src/ansys/dpf/post/mesh.py @@ -27,6 +27,8 @@ class Mesh: def __init__(self, meshed_region: dpf.MeshedRegion): """Initialize this class.""" + if meshed_region is None: + raise ValueError("Tried to instantiate an empty Mesh.") self._meshed_region = meshed_region def __str__(self): diff --git a/tests/test_mesh.py b/tests/test_mesh.py index 7a1f431b1..4ec8c9c5b 100644 --- a/tests/test_mesh.py +++ b/tests/test_mesh.py @@ -1,8 +1,9 @@ import ansys.dpf.core as dpf import numpy as np +import pytest from pytest import fixture -from ansys.dpf.post import StaticMechanicalSimulation +from ansys.dpf.post import Mesh, StaticMechanicalSimulation @fixture @@ -14,6 +15,8 @@ def mesh(static_rst): def test_mesh_core_object(mesh): assert isinstance(mesh._core_object, dpf.MeshedRegion) assert mesh._core_object.nodes.n_nodes == 81 + with pytest.raises(ValueError, match="Tried to instantiate an empty Mesh."): + _ = Mesh(None) def test_mesh_node_ids(mesh): From 19f214908cc805eef94813ba6808cbed8b3a33ab Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Thu, 20 Jul 2023 19:30:30 +0200 Subject: [PATCH 98/99] Working 05-mesh-exploration.py --- .../05-mesh-exploration.py | 257 ++++++++++-------- 1 file changed, 138 insertions(+), 119 deletions(-) diff --git a/examples/01-Detailed-Examples/05-mesh-exploration.py b/examples/01-Detailed-Examples/05-mesh-exploration.py index 664b80c11..51f0fcfd8 100644 --- a/examples/01-Detailed-Examples/05-mesh-exploration.py +++ b/examples/01-Detailed-Examples/05-mesh-exploration.py @@ -3,14 +3,15 @@ Explore the mesh ================ -In this script a static simulation is used as an example to show how to -query mesh information such as connectivity, element IDs, element types and so on. +This example shows how to explore and manipulate the mesh object to query mesh data +such as connectivity tables, element IDs, element types and so on. """ ############################################################################### # Perform required imports # ------------------------ -# Perform required imports. # This example uses a supplied file that you can +# Perform required imports. +# This example uses a supplied file that you can # get by importing the DPF ``examples`` package. from ansys.dpf import post @@ -18,186 +19,204 @@ from ansys.dpf.post.common import elemental_properties ############################################################################### -# Get ``Simulation`` object -# ------------------------- -# Get the ``Simulation`` object that allows access to the result. The ``Simulation`` -# object must be instantiated with the path for the result file. For example, -# ``"C:/Users/user/my_result.rst"`` on Windows or ``"/home/user/my_result.rst"`` -# on Linux. - -example_path = examples.download_all_kinds_of_complexity() -simulation = post.StaticMechanicalSimulation(example_path) +# Load the result file +# -------------------- +# Load the result file in a ``Simulation`` object that allows access to the results. +# The ``Simulation`` object must be instantiated with the path for the result file. +# For example, ``"C:/Users/user/my_result.rst"`` on Windows +# or ``"/home/user/my_result.rst"`` on Linux. -# print the simulation to get an overview of what's available -print(simulation) +example_path = examples.download_harmonic_clamped_pipe() +simulation = post.HarmonicMechanicalSimulation(example_path) ############################################################################### # Get the mesh # ------------ - -# Retrieve the actual mesh +# Retrieve the mesh mesh = simulation.mesh +# Printing the mesh directly gives the same information already shown at the simulation level +print(mesh) ############################################################################### +# Plot the mesh +# ------------- +# Plot the mesh to view the bare mesh of the model +mesh.plot() + +#################################################################q############## # Query basic information about the mesh # -------------------------------------- +# The ``Mesh`` object has several properties allowing access to different information such as: -# Node IDs -n_ids = mesh.node_ids +# the number of nodes +print(f"This mesh contains {mesh.num_nodes} nodes") -# Element IDs -e_ids = mesh.element_ids +# the list of node IDs +print(f"with IDs: {mesh.node_ids}.") -# Available named selection names -named_selections = mesh.named_selections.keys() +# the number of elements +print(f"This mesh contains {mesh.num_elements} elements") -# Number of nodes -n_nodes = mesh.num_nodes +# the list of element IDs +print(f"with IDs {mesh.element_ids}.") -# Number of elements -n_elements = mesh.num_elements +# the unit of the mesh +print(f"The mesh is in '{mesh.unit}'.") -# Number of named selections -n_ns = len(named_selections) - -# Unit (get and set) -mesh_unit = mesh.unit -mesh.unit = "mm" - -print(n_ids) -print(e_ids) +############################################################################### +# Named selections +# ---------------- +# The available named selections are given as a dictionary +# with the names as keys and the actual ``NamedSelection`` objects as values. +# Printing the dictionary informs you on the available names. +named_selections = mesh.named_selections print(named_selections) -print(n_nodes) -print(n_elements) ############################################################################### -# Get Named Selections -# -------------------- -ns_list = mesh.named_selections.keys() -first_key = ns_list[0] -named_selection = mesh.named_selections[first_key] - -for k in mesh.named_selections.keys(): - print(k) -for v in mesh.named_selections.values(): - print(v) -for k, v in mesh.named_selections.items(): - print(f"{k} = {v}") +# To get a specific named selection, query it using its name as key +print(named_selections["_FIXEDSU"]) ############################################################################### -# Get elements -# ------------ - -# Get an element by ID -el_by_id = mesh.elements.by_id[1] - -# Get an element by index -index = el_by_id.index -print(mesh.elements[index]) +# Elements +# -------- +# Use ``mesh.elements`` to access the list of Element objects +print(mesh.elements) ############################################################################### -# Element Types and Materials -# --------------------------- - -# Get the element types -el_types = mesh.element_types -print(el_types) +# You can then query a specific element by its ID +print(mesh.elements.by_id[1]) -# Get the materials -e_materials = mesh.materials -print(e_materials) +############################################################################### +# or by its index +element_0 = mesh.elements[0] +print(element_0) ############################################################################### # Query information about one particular element # ---------------------------------------------- +# You can request the IDs of the nodes attached to an ``Element`` object +print(element_0.node_ids) -# Get the nodes of an element -elem_0_nodes = mesh.elements[0].nodes +# or the list of ``Node`` objects +print(element_0.nodes) -# Get the node IDs of an element -elem_0_nodes_ids = mesh.elements[0].node_ids - -# Get the number of nodes of an element -num_nodes_elem_0 = mesh.elements[0].num_nodes +# To get the number of nodes attached, use +print(element_0.num_nodes) # Get the type of the element -elem_0_type_info = mesh.elements[0].type_info -elem_0_type = mesh.elements[0].type +print(element_0.type_info) +print(element_0.type) # Get the shape of the element -elem_0_shape = mesh.elements[0].shape +print(element_0.shape) + +############################################################################### +# Element types and materials +# --------------------------- +# The ``Mesh`` object provides access to properties defined on all elements, +# such as their types or their associated materials. + +# Get the type of all elements +print(mesh.element_types) + +# Get the materials of all elements +print(mesh.materials) ############################################################################### -# Get the elemental connectivity -# ------------------------------ +# Elemental connectivity +# ---------------------- +# The elemental connectivity maps elements to connected nodes, either using IDs or indexes. + +# To access the indexes of the connected nodes using an element's index, use +element_to_node_connectivity = mesh.element_to_node_connectivity +print(element_to_node_connectivity[0]) -# get node indices from element index -conn1 = mesh.element_to_node_connectivity +# To access the IDs of the connected nodes using an element's index, use +element_to_node_ids_connectivity = mesh.element_to_node_ids_connectivity +print(element_to_node_ids_connectivity[0]) -# get node IDs from element index -conn2 = mesh.element_to_node_ids_connectivity +############################################################################### +# Each connectivity object has a ``by_id`` property which changes the input from index to ID, thus: +# To access the indexes of the connected nodes using an element's ID, use +element_to_node_connectivity_by_id = mesh.element_to_node_connectivity.by_id +print(element_to_node_connectivity_by_id[3487]) -el_idx_5 = mesh.elements[5] -# get node IDS from element ID -node_ids = conn2.by_id[el_idx_5.id] +# To access the IDs of the connected nodes using an element's ID, use +element_to_node_ids_connectivity_by_id = mesh.element_to_node_ids_connectivity.by_id +print(element_to_node_ids_connectivity_by_id[3487]) ############################################################################### -# Get nodes -# --------- +# Nodes +# ----- -# Get a node by ID -node_by_id = mesh.nodes.by_id[1] +# Get a node by its ID +node_1 = mesh.nodes.by_id[1] +print(node_1) -# Get a node by index -node_by_index = mesh.nodes[0] +############################################################################### +# Get a node by its index +print(mesh.nodes[0]) +############################################################################### # Get the coordinates of all nodes print(mesh.coordinates) ############################################################################### # Query information about one particular node # ------------------------------------------- +# Get the coordinates of a node +print(node_1.coordinates) -# Coordinates -node_1 = mesh.nodes[1].coordinates +############################################################################### +# Nodal connectivity +# ------------------ +# The nodal connectivity maps nodes to connected elements, either using IDs or indexes. -# Get Nodal connectivity -conn3 = mesh.node_to_element_connectivity -conn4 = mesh.node_to_element_ids_connectivity +# To access the indexes of the connected elements using a node's index, use +node_to_element_connectivity = mesh.node_to_element_connectivity +print(node_to_element_connectivity[0]) -print(mesh.nodes[0]) +# To access the IDs of the connected elements using a node's index, use +node_to_element_ids_connectivity = mesh.node_to_element_ids_connectivity +print(node_to_element_ids_connectivity[0]) -# elements indices to elem_id -print(conn3[0]) +############################################################################### +# Each connectivity object has a ``by_id`` property which changes the input from index to ID, thus: +# To access the indexes of the connected elements using a node's ID, use +node_to_element_connectivity_by_id = mesh.node_to_element_connectivity.by_id +print(node_to_element_connectivity_by_id[1]) -# elements IDs from node index -print(conn4[0]) +# To access the IDs of the connected elements using a node's ID, use +node_to_element_ids_connectivity_by_id = mesh.node_to_element_ids_connectivity.by_id +print(node_to_element_ids_connectivity_by_id[1]) ############################################################################### # Splitting into meshes # --------------------- - -# Plot the mesh -mesh.plot() - -# Split the global mesh according to mesh properties +# You can split the global mesh according to mesh properties to work on specific parts of the mesh meshes = simulation.split_mesh_by_properties( properties=[elemental_properties.material, elemental_properties.element_shape] ) -meshes.plot() - -# Split the global mesh and select meshes for specific property values +# The object obtained is a ``Meshes`` print(meshes) -meshes = simulation.split_mesh_by_properties( + +# Plotting a ``Meshes`` object plots a combination of all the ``Mesh`` objects within +meshes.plot(text="Mesh split") + +############################################################################### +# Select a specific ``Mesh`` in the ``Meshes``, by index +meshes[0].plot(text="First mesh in the split mesh") + +############################################################################### +# You can split the global mesh and select meshes based on specific property values +meshes_filtered = simulation.split_mesh_by_properties( properties={ - elemental_properties.material: 1, - elemental_properties.element_shape: [0, 1], + elemental_properties.material: [2, 3, 4], + elemental_properties.element_shape: 1, } ) +meshes_filtered.plot(text="Mesh split and filtered") -meshes.plot() - -# Select a specific Mesh in the Meshes, by index -meshes[0].plot() -# or by property values -meshes[{"mat": 1, "elshape": 0}].plot() +############################################################################### +# or with a unique combination of property values +meshes[{"mat": 5, "elshape": 0}].plot(text="Mesh for mat=5 and elshape=0") From 3e271a4ef0d5be44de36ab73845dc3b75d0373c8 Mon Sep 17 00:00:00 2001 From: "paul.profizi" Date: Fri, 21 Jul 2023 09:57:10 +0200 Subject: [PATCH 99/99] Improve notebook formatting of 05-mesh-exploration.py --- .../05-mesh-exploration.py | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/examples/01-Detailed-Examples/05-mesh-exploration.py b/examples/01-Detailed-Examples/05-mesh-exploration.py index 51f0fcfd8..50045de6d 100644 --- a/examples/01-Detailed-Examples/05-mesh-exploration.py +++ b/examples/01-Detailed-Examples/05-mesh-exploration.py @@ -32,9 +32,8 @@ ############################################################################### # Get the mesh # ------------ -# Retrieve the mesh +# Retrieve the mesh and print it mesh = simulation.mesh -# Printing the mesh directly gives the same information already shown at the simulation level print(mesh) ############################################################################### @@ -43,25 +42,30 @@ # Plot the mesh to view the bare mesh of the model mesh.plot() -#################################################################q############## +############################################################################### # Query basic information about the mesh # -------------------------------------- # The ``Mesh`` object has several properties allowing access to different information such as: +############################################################################### # the number of nodes print(f"This mesh contains {mesh.num_nodes} nodes") +############################################################################### # the list of node IDs -print(f"with IDs: {mesh.node_ids}.") +print(f"with IDs: {mesh.node_ids}") +############################################################################### # the number of elements print(f"This mesh contains {mesh.num_elements} elements") +############################################################################### # the list of element IDs -print(f"with IDs {mesh.element_ids}.") +print(f"with IDs {mesh.element_ids}") +############################################################################### # the unit of the mesh -print(f"The mesh is in '{mesh.unit}'.") +print(f"The mesh is in '{mesh.unit}'") ############################################################################### # Named selections @@ -92,21 +96,25 @@ print(element_0) ############################################################################### -# Query information about one particular element -# ---------------------------------------------- +# Query information about a particular element +# -------------------------------------------- # You can request the IDs of the nodes attached to an ``Element`` object print(element_0.node_ids) +############################################################################### # or the list of ``Node`` objects print(element_0.nodes) +############################################################################### # To get the number of nodes attached, use print(element_0.num_nodes) +############################################################################### # Get the type of the element print(element_0.type_info) print(element_0.type) +############################################################################### # Get the shape of the element print(element_0.shape) @@ -116,9 +124,11 @@ # The ``Mesh`` object provides access to properties defined on all elements, # such as their types or their associated materials. +############################################################################### # Get the type of all elements print(mesh.element_types) +############################################################################### # Get the materials of all elements print(mesh.materials) @@ -127,20 +137,25 @@ # ---------------------- # The elemental connectivity maps elements to connected nodes, either using IDs or indexes. +############################################################################### # To access the indexes of the connected nodes using an element's index, use element_to_node_connectivity = mesh.element_to_node_connectivity print(element_to_node_connectivity[0]) +############################################################################### # To access the IDs of the connected nodes using an element's index, use element_to_node_ids_connectivity = mesh.element_to_node_ids_connectivity print(element_to_node_ids_connectivity[0]) ############################################################################### # Each connectivity object has a ``by_id`` property which changes the input from index to ID, thus: + +############################################################################### # To access the indexes of the connected nodes using an element's ID, use element_to_node_connectivity_by_id = mesh.element_to_node_connectivity.by_id print(element_to_node_connectivity_by_id[3487]) +############################################################################### # To access the IDs of the connected nodes using an element's ID, use element_to_node_ids_connectivity_by_id = mesh.element_to_node_ids_connectivity.by_id print(element_to_node_ids_connectivity_by_id[3487]) @@ -148,7 +163,6 @@ ############################################################################### # Nodes # ----- - # Get a node by its ID node_1 = mesh.nodes.by_id[1] print(node_1) @@ -172,20 +186,25 @@ # ------------------ # The nodal connectivity maps nodes to connected elements, either using IDs or indexes. +############################################################################### # To access the indexes of the connected elements using a node's index, use node_to_element_connectivity = mesh.node_to_element_connectivity print(node_to_element_connectivity[0]) +############################################################################### # To access the IDs of the connected elements using a node's index, use node_to_element_ids_connectivity = mesh.node_to_element_ids_connectivity print(node_to_element_ids_connectivity[0]) ############################################################################### # Each connectivity object has a ``by_id`` property which changes the input from index to ID, thus: + +############################################################################### # To access the indexes of the connected elements using a node's ID, use node_to_element_connectivity_by_id = mesh.node_to_element_connectivity.by_id print(node_to_element_connectivity_by_id[1]) +############################################################################### # To access the IDs of the connected elements using a node's ID, use node_to_element_ids_connectivity_by_id = mesh.node_to_element_ids_connectivity.by_id print(node_to_element_ids_connectivity_by_id[1]) @@ -197,9 +216,11 @@ meshes = simulation.split_mesh_by_properties( properties=[elemental_properties.material, elemental_properties.element_shape] ) +############################################################################### # The object obtained is a ``Meshes`` print(meshes) +############################################################################### # Plotting a ``Meshes`` object plots a combination of all the ``Mesh`` objects within meshes.plot(text="Mesh split")