From e28a779bb3e6536129318eb44a68224c210f6c05 Mon Sep 17 00:00:00 2001 From: Mainak Kundu Date: Mon, 6 Jan 2025 16:53:24 -0500 Subject: [PATCH 1/7] feat: Retrieve mesh data in solution mode --- src/ansys/fluent/core/services/field_data.py | 59 ++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/ansys/fluent/core/services/field_data.py b/src/ansys/fluent/core/services/field_data.py index ff81cf150be..ab063b1fe9e 100644 --- a/src/ansys/fluent/core/services/field_data.py +++ b/src/ansys/fluent/core/services/field_data.py @@ -2,6 +2,7 @@ from enum import Enum from functools import reduce +import io from typing import Callable, Dict, List, Tuple import grpc @@ -82,6 +83,36 @@ def get_fields(self, request): ) return chunk_iterator + def get_solver_mesh_nodes( + self, request: FieldDataProtoModule.GetSolverMeshNodesRequest + ): + """GetSolverMeshNodesDouble RPC of FieldData service.""" + chuncked_responses = self._stub.GetSolverMeshNodesDouble( + request, metadata=self._metadata + ) + buffer = io.BytesIO() + for chuncked_response in chuncked_responses: + buffer.write(chuncked_response.chunk) + serialized_response = buffer.getvalue() + response = FieldDataProtoModule.GetSolverMeshNodesDoubleResponse() + response.ParseFromString(serialized_response) + return response + + def get_solver_mesh_elements( + self, request: FieldDataProtoModule.GetSolverMeshElementsRequest + ): + """GetSolverMeshElements RPC of FieldData service.""" + chuncked_responses = self._stub.GetSolverMeshElements( + request, metadata=self._metadata + ) + buffer = io.BytesIO() + for chuncked_response in chuncked_responses: + buffer.write(chuncked_response.chunk) + serialized_response = buffer.getvalue() + response = FieldDataProtoModule.GetSolverMeshElementsResponse() + response.ParseFromString(serialized_response) + return response + class FieldInfo: """Provides access to Fluent field information. @@ -1285,3 +1316,31 @@ def get_pathlines_field_data( field_name: pathlines_data[surface_ids[count]][field_name], } return path_lines_dict + + def get_mesh_nodes(self, zone_id: int): + """Get mesh nodes for a zone. + + Parameters + ---------- + zone_id : int + Zone ID. + """ + request = FieldDataProtoModule.GetSolverMeshNodesRequest( + domain_id=1, thread_id=zone_id + ) + response = self._service.get_solver_mesh_nodes(request) + return response.nodes + + def get_mesh_elements(self, zone_id: int): + """Get mesh elements for a zone. + + Parameters + ---------- + zone_id : int + Zone ID. + """ + request = FieldDataProtoModule.GetSolverMeshElementsRequest( + domain_id=1, thread_id=zone_id + ) + response = self._service.get_solver_mesh_elements(request) + return response.elements From 8855aef335629f8b3c50e63cacc82171d2566f48 Mon Sep 17 00:00:00 2001 From: Mainak Kundu Date: Tue, 7 Jan 2025 13:09:47 -0500 Subject: [PATCH 2/7] feat: Retrieve mesh data in solution mode --- src/ansys/fluent/core/services/field_data.py | 117 ++++++++++++++++--- 1 file changed, 104 insertions(+), 13 deletions(-) diff --git a/src/ansys/fluent/core/services/field_data.py b/src/ansys/fluent/core/services/field_data.py index ab063b1fe9e..8af62b9957d 100644 --- a/src/ansys/fluent/core/services/field_data.py +++ b/src/ansys/fluent/core/services/field_data.py @@ -1,5 +1,6 @@ """Wrappers over FieldData gRPC service of Fluent.""" +from dataclasses import dataclass from enum import Enum from functools import reduce import io @@ -953,6 +954,88 @@ def _extract_field(field_datatype, field_size, chunk_iterator): return fields_data +@dataclass +class Node: + """Node class for mesh. + + Attributes: + ----------- + x : float + x-coordinate of the node. + y : float + y-coordinate of the node. + z : float + z-coordinate of the node. + """ + + _id: int + x: float + y: float + z: float + + +class CellElementType(Enum): + """Element types for a cell element.""" + + # 3 nodes, 3 faces + TRIANGLE = 1 + # 4 nodes, 4 faces + TETRAHEDRON = 2 + # 4 nodes, 4 faces + QUADRILATERAL = 3 + # 8 nodes, 6 faces + HEXAHEDRON = 4 + # 5 nodes, 5 faces + PYRAMID = 5 + # 6 nodes, 5 faces + WEDGE = 6 + # Arbitrary number of nodes and faces + POLYHEDRON = 7 + # 2 nodes, 1 face (only in 2D) + GHOST = 8 + # 10 nodes, 4 faces + QUADRATIC_TETRAHEDRON = 9 + # 20 nodes, 6 faces + QUADRATIC_HEXAHEDRON = 10 + # 13 nodes, 5 faces + QUADRATIC_PYRAMID = 11 + # 15 nodes, 5 faces + QUADRATIC_WEDGE = 12 + + +@dataclass +class Element: + """Element class for mesh. + + Attributes: + ----------- + element_type : CellElementType + Element type of the element. + node_indices : list[int] + 0-based node indices of the element. + """ + + _id: int + element_type: CellElementType + node_indices: list[int] + + +@dataclass +class Mesh: + """Mesh class for Fluent field data. + + Attributes: + ----------- + nodes : list[Node] + List of nodes in the mesh. + elements : list[Element] + List of elements in the mesh. + """ + + nodes: list[Node] + elements: list[Element] + + class FieldData: """Provides access to Fluent field data on surfaces.""" @@ -1317,30 +1400,38 @@ def get_pathlines_field_data( } return path_lines_dict - def get_mesh_nodes(self, zone_id: int): - """Get mesh nodes for a zone. + def get_mesh(self, zone_id: int) -> Mesh: + """Get mesh for a zone. Parameters ---------- zone_id : int Zone ID. + + Returns + ------- + Mesh + Mesh object containing nodes and elements. """ request = FieldDataProtoModule.GetSolverMeshNodesRequest( domain_id=1, thread_id=zone_id ) response = self._service.get_solver_mesh_nodes(request) - return response.nodes - - def get_mesh_elements(self, zone_id: int): - """Get mesh elements for a zone. - - Parameters - ---------- - zone_id : int - Zone ID. - """ + nodes = response.nodes + nodes = [Node(_id=node.id, x=node.x, y=node.y, z=node.z) for node in nodes] + nodes = sorted(nodes, key=lambda x: x._id) request = FieldDataProtoModule.GetSolverMeshElementsRequest( domain_id=1, thread_id=zone_id ) response = self._service.get_solver_mesh_elements(request) - return response.elements + elements = response.elements + elements = [ + Element( + _id=element.id, + element_type=CellElementType(element.element_type), + node_indices=[(id - 1) for id in element.node_ids], + ) + for element in elements + ] + elements = sorted(elements, key=lambda x: x._id) + return Mesh(nodes=nodes, elements=elements) From c286e0267075a47a9368fe41a0958650dff661c8 Mon Sep 17 00:00:00 2001 From: Mainak Kundu Date: Tue, 7 Jan 2025 20:07:05 -0500 Subject: [PATCH 3/7] build: update ansys-api-fluent version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a3f2faddac2..d2b0eed0098 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ classifiers = [ "Operating System :: OS Independent", ] dependencies = [ - "ansys-api-fluent>=0.3.30", + "ansys-api-fluent>=0.3.31", "ansys-platform-instancemanagement~=1.0", "ansys-tools-filetransfer>=0.1,<0.3", "ansys-units>=0.3.3,<0.5", From 90391a6e8cf4de478d5052e4baee3549b03d9df4 Mon Sep 17 00:00:00 2001 From: Mainak Kundu Date: Wed, 8 Jan 2025 02:55:02 -0500 Subject: [PATCH 4/7] test: mesh data --- tests/test_field_data.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/tests/test_field_data.py b/tests/test_field_data.py index 87e337f003a..c18b79a06ba 100644 --- a/tests/test_field_data.py +++ b/tests/test_field_data.py @@ -1,10 +1,15 @@ import numpy as np import pytest +from test_utils import pytest_approx from ansys.fluent.core import examples from ansys.fluent.core.examples.downloads import download_file from ansys.fluent.core.exceptions import DisallowedValuesError -from ansys.fluent.core.services.field_data import FieldUnavailable, SurfaceDataType +from ansys.fluent.core.services.field_data import ( + CellElementType, + FieldUnavailable, + SurfaceDataType, +) HOT_INLET_TEMPERATURE = 313.15 @@ -464,3 +469,19 @@ def plot_mesh(index, field_name, data): assert len(mesh_data[5]["faces"]) == 80 assert list(mesh_data[12].keys()) == ["vertices", "faces"] + + +@pytest.mark.fluent_version(">=25.2") +def test_mesh_data(static_mixer_case_session): + solver = static_mixer_case_session + mesh = solver.fields.field_data.get_mesh(zone_id=97) + assert len(mesh.nodes) == 82247 + assert len(mesh.elements) == 22771 + assert mesh.elements[0].element_type == CellElementType.POLYHEDRON + assert len(mesh.elements[0].node_indices) == 19 + assert min(mesh.nodes, key=lambda x: x.x).x == pytest_approx(-1.999075e-03) + assert max(mesh.nodes, key=lambda x: x.x).x == pytest_approx(1.999125e-03) + assert min(mesh.nodes, key=lambda x: x.y).y == pytest_approx(-3.000000e-03) + assert max(mesh.nodes, key=lambda x: x.y).y == pytest_approx(3.000000e-03) + assert min(mesh.nodes, key=lambda x: x.z).z == pytest_approx(-2.000000e-03) + assert max(mesh.nodes, key=lambda x: x.z).z == pytest_approx(2.500000e-03) From fa7f09a0e3e6ff88eb5c443df17c83cd5b8c0fec Mon Sep 17 00:00:00 2001 From: Mainak Kundu Date: Thu, 9 Jan 2025 10:22:57 -0500 Subject: [PATCH 5/7] feat: unsort element data --- src/ansys/fluent/core/services/field_data.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ansys/fluent/core/services/field_data.py b/src/ansys/fluent/core/services/field_data.py index 8af62b9957d..996e2a0fa06 100644 --- a/src/ansys/fluent/core/services/field_data.py +++ b/src/ansys/fluent/core/services/field_data.py @@ -1027,9 +1027,10 @@ class Mesh: Attributes: ----------- nodes : list[Node] - List of nodes in the mesh. + List of nodes in the mesh. Sorted by Fluent's node ID. elements : list[Element] - List of elements in the mesh. + List of elements in the mesh. Unsorted as we want to correlate with the + solution data retrieved via svar service. """ nodes: list[Node] @@ -1433,5 +1434,4 @@ def get_mesh(self, zone_id: int) -> Mesh: ) for element in elements ] - elements = sorted(elements, key=lambda x: x._id) return Mesh(nodes=nodes, elements=elements) From 8fd1e5d7f79c281f15323abe77dbd27c29035f0b Mon Sep 17 00:00:00 2001 From: Mainak Kundu Date: Thu, 9 Jan 2025 11:47:20 -0500 Subject: [PATCH 6/7] test: fix --- tests/test_field_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_field_data.py b/tests/test_field_data.py index c18b79a06ba..04dafa03690 100644 --- a/tests/test_field_data.py +++ b/tests/test_field_data.py @@ -478,7 +478,7 @@ def test_mesh_data(static_mixer_case_session): assert len(mesh.nodes) == 82247 assert len(mesh.elements) == 22771 assert mesh.elements[0].element_type == CellElementType.POLYHEDRON - assert len(mesh.elements[0].node_indices) == 19 + assert len(mesh.elements[0].node_indices) == 14 assert min(mesh.nodes, key=lambda x: x.x).x == pytest_approx(-1.999075e-03) assert max(mesh.nodes, key=lambda x: x.x).x == pytest_approx(1.999125e-03) assert min(mesh.nodes, key=lambda x: x.y).y == pytest_approx(-3.000000e-03) From 8f917d5d04de36090545d9a71adfef875e1ac363 Mon Sep 17 00:00:00 2001 From: Mainak Kundu Date: Thu, 9 Jan 2025 11:51:51 -0500 Subject: [PATCH 7/7] test: fix --- tests/test_field_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_field_data.py b/tests/test_field_data.py index 04dafa03690..0757a7254cd 100644 --- a/tests/test_field_data.py +++ b/tests/test_field_data.py @@ -478,7 +478,7 @@ def test_mesh_data(static_mixer_case_session): assert len(mesh.nodes) == 82247 assert len(mesh.elements) == 22771 assert mesh.elements[0].element_type == CellElementType.POLYHEDRON - assert len(mesh.elements[0].node_indices) == 14 + assert len(mesh.elements[0].node_indices) > 0 assert min(mesh.nodes, key=lambda x: x.x).x == pytest_approx(-1.999075e-03) assert max(mesh.nodes, key=lambda x: x.x).x == pytest_approx(1.999125e-03) assert min(mesh.nodes, key=lambda x: x.y).y == pytest_approx(-3.000000e-03)