From 167a10b5d878d4ce293449744ebe69459238e874 Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Tue, 5 Aug 2025 10:16:29 +0200 Subject: [PATCH 1/9] refactor: Refactor import statements in test_result.py to use core module - Updated import paths for COMPONENTS and DPFResult from ansys.mapdl.core.reader.result to ansys.mapdl.core.reader.core. - This change aligns with the new module structure and ensures consistency across the test suite. --- src/ansys/mapdl/core/reader/__init__.py | 2 +- src/ansys/mapdl/core/reader/{result.py => core.py} | 0 tests/test_result.py | 12 ++++++------ 3 files changed, 7 insertions(+), 7 deletions(-) rename src/ansys/mapdl/core/reader/{result.py => core.py} (100%) diff --git a/src/ansys/mapdl/core/reader/__init__.py b/src/ansys/mapdl/core/reader/__init__.py index d0ad60186c8..6b8181749e2 100644 --- a/src/ansys/mapdl/core/reader/__init__.py +++ b/src/ansys/mapdl/core/reader/__init__.py @@ -20,4 +20,4 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from .result import DPFResult +from ansys.mapdl.core.reader.core import DPFResult diff --git a/src/ansys/mapdl/core/reader/result.py b/src/ansys/mapdl/core/reader/core.py similarity index 100% rename from src/ansys/mapdl/core/reader/result.py rename to src/ansys/mapdl/core/reader/core.py diff --git a/tests/test_result.py b/tests/test_result.py index 035bb78d64c..69209b983f9 100644 --- a/tests/test_result.py +++ b/tests/test_result.py @@ -66,7 +66,7 @@ else: from ansys.dpf import core as dpf_core from ansys.dpf.gate.errors import DPFServerException - from ansys.mapdl.core.reader.result import COMPONENTS + from ansys.mapdl.core.reader.core import COMPONENTS from ansys.mapdl.reader import read_binary from ansys.mapdl.reader.rst import Result @@ -312,7 +312,7 @@ def post(self, setup): def result(self, setup, tmp_path_factory, mapdl): # Since the DPF upload is broken, we copy the RST file to a temporary directory # in the MAPDL directory - from ansys.mapdl.core.reader.result import DPFResult + from ansys.mapdl.core.reader import DPFResult LOG.debug( f"Creating DPFResult with RST file: {self.rst_path}", @@ -375,7 +375,7 @@ def test__get_entities_ids_ints(self, result): def test_error_initialization_none(): - from ansys.mapdl.core.reader.result import DPFResult + from ansys.mapdl.core.reader import DPFResult with pytest.raises( ValueError, match="One of the following kwargs must be supplied" @@ -384,14 +384,14 @@ def test_error_initialization_none(): def test_error_initialization_both(): - from ansys.mapdl.core.reader.result import DPFResult + from ansys.mapdl.core.reader import DPFResult with pytest.raises(ValueError, match="Only one the arguments must be supplied"): DPFResult(rst_file="", mapdl="") def test_error_initialization_rst_file_not_found(): - from ansys.mapdl.core.reader.result import DPFResult + from ansys.mapdl.core.reader import DPFResult rst_file = "my_unexisting_rst_file.rst" with pytest.raises( @@ -451,7 +451,7 @@ def post(self): @pytest.fixture(scope="class") def result(self, mapdl): """Fixture to ensure the model is solved before running tests.""" - from ansys.mapdl.core.reader.result import DPFResult + from ansys.mapdl.core.reader import DPFResult clear(mapdl) solved_box_func(mapdl) From 76362af3b05557e064484f0be88b0c6dacb0febe Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Tue, 5 Aug 2025 10:22:42 +0200 Subject: [PATCH 2/9] feat: add constants and types for the MAPDL reader module --- src/ansys/mapdl/core/reader/constants.py | 92 ++++++++++++++++++ src/ansys/mapdl/core/reader/core.py | 114 ++++------------------- src/ansys/mapdl/core/reader/types.py | 50 ++++++++++ 3 files changed, 162 insertions(+), 94 deletions(-) create mode 100644 src/ansys/mapdl/core/reader/constants.py create mode 100644 src/ansys/mapdl/core/reader/types.py diff --git a/src/ansys/mapdl/core/reader/constants.py b/src/ansys/mapdl/core/reader/constants.py new file mode 100644 index 00000000000..a352b81fc30 --- /dev/null +++ b/src/ansys/mapdl/core/reader/constants.py @@ -0,0 +1,92 @@ +# Copyright (C) 2016 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Defines constants for the MAPDL reader.""" + + +## Globals +COMPONENTS: list[str] = ["X", "Y", "Z", "XY", "YZ", "XZ"] + +LOCATION_MAPPING: dict[str, str] = { + "NODE": "Nodal", + "ELEM": "Elemental", +} + +MATERIAL_PROPERTIES: list[str] = [ + "EX", + "EY", + "EZ", + "ALPX", + "ALPY", + "ALPZ", + "REFT", + "PRXY", + "PRYZ", + "PRX", + "NUXY", + "NUYZ", + "NUXZ", + "GXY", + "GYZ", + "GXZ", + "DAMP", + "MU", + "DENS", + "C", + "ENTH", + "KXX", + "KYY", + "KZZ", + "HF", + "EMIS", + "QRATE", + "VISC", + "SONC", + "RSVX", + "RSVY", + "RSVZ", + "PERX", + "PERY", + "PERZ", + "MURX", + "MURY", + "MURZ", + "MGXX", + "MGYY", + "MGZZ", + "XTEN", + "XCMP", + "YTEN", + "YCMP", + "ZTEN", + "ZCMP", + "XY", + "YZ", + "XZ", + "XYCP", + "YZCP", + "XZCP", + "XZIT", + "XZIC", + "YZIT", + "YZIC", +] diff --git a/src/ansys/mapdl/core/reader/core.py b/src/ansys/mapdl/core/reader/core.py index 9cd5e866d6b..735100b8012 100644 --- a/src/ansys/mapdl/core/reader/core.py +++ b/src/ansys/mapdl/core/reader/core.py @@ -31,9 +31,6 @@ Callable, Iterable, Literal, - ParamSpec, - TypeAlias, - Union, ) import weakref @@ -52,97 +49,26 @@ if TYPE_CHECKING and _HAS_PYVISTA: import pyvista as pv -## Types -Rnum: TypeAlias = Union[int, float, Iterable[int], Iterable[float], None] -Ids: TypeAlias = Union[int, Iterable[int], None] -Locations: TypeAlias = Literal["Nodal", "Elemental"] - -Entities: TypeAlias = str | int | Iterable[str | int] | None -EntityType: TypeAlias = Literal["Nodal", "Elemental", "ElementalNodal"] - -ResultField: TypeAlias = str # To be defined later.. Eg "displacement" etc... -SolutionType: TypeAlias = str -ComponentsDirections: TypeAlias = Literal["X", "Y", "Z", "XY", "YZ", "XZ"] - -Nodes: TypeAlias = str | int | Iterable[int | str] | None -Elements: TypeAlias = str | int | Iterable[int | str] | None -MAPDLComponents: TypeAlias = str | Iterable[int | str] | None - -ReturnData: TypeAlias = tuple[ - np.ndarray[Any, np.dtype[np.floating[Any]]], - np.ndarray[Any, np.dtype[np.floating[Any]]], -] -Kwargs: TypeAlias = dict[Any, Any] -P = ParamSpec("P") - -## Globals -COMPONENTS: list[str] = ["X", "Y", "Z", "XY", "YZ", "XZ"] - -LOCATION_MAPPING: dict[str, str] = { - "NODE": "Nodal", - "ELEM": "Elemental", -} - -MATERIAL_PROPERTIES: list[str] = [ - "EX", - "EY", - "EZ", - "ALPX", - "ALPY", - "ALPZ", - "REFT", - "PRXY", - "PRYZ", - "PRX", - "NUXY", - "NUYZ", - "NUXZ", - "GXY", - "GYZ", - "GXZ", - "DAMP", - "MU", - "DENS", - "C", - "ENTH", - "KXX", - "KYY", - "KZZ", - "HF", - "EMIS", - "QRATE", - "VISC", - "SONC", - "RSVX", - "RSVY", - "RSVZ", - "PERX", - "PERY", - "PERZ", - "MURX", - "MURY", - "MURZ", - "MGXX", - "MGYY", - "MGZZ", - "XTEN", - "XCMP", - "YTEN", - "YCMP", - "ZTEN", - "ZCMP", - "XY", - "YZ", - "XZ", - "XYCP", - "YZCP", - "XZCP", - "XZIT", - "XZIC", - "YZIT", - "YZIC", -] - +from ansys.mapdl.core.reader.constants import ( + LOCATION_MAPPING, + MATERIAL_PROPERTIES, +) +from ansys.mapdl.core.reader.types import ( + ComponentsDirections, + Elements, + Entities, + EntityType, + Ids, + Kwargs, + Locations, + MAPDLComponents, + Nodes, + P, + ResultField, + ReturnData, + Rnum, + SolutionType, +) NOT_AVAILABLE_METHOD = """The method '{method}' has not been ported to the new DPF-based Results backend. If you still want to use it, you can switch to 'pymapdl-reader' backend.""" diff --git a/src/ansys/mapdl/core/reader/types.py b/src/ansys/mapdl/core/reader/types.py new file mode 100644 index 00000000000..ca865a472a8 --- /dev/null +++ b/src/ansys/mapdl/core/reader/types.py @@ -0,0 +1,50 @@ +# Copyright (C) 2016 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Types for the MAPDL reader module.""" + +from typing import Any, Iterable, Literal, ParamSpec, TypeAlias, Union + +import numpy as np + +## Types +Rnum: TypeAlias = Union[int, float, Iterable[int], Iterable[float], None] +Ids: TypeAlias = Union[int, Iterable[int], None] +Locations: TypeAlias = Literal["Nodal", "Elemental"] + +Entities: TypeAlias = str | int | Iterable[str | int] | None +EntityType: TypeAlias = Literal["Nodal", "Elemental", "ElementalNodal"] + +ResultField: TypeAlias = str # To be defined later.. Eg "displacement" etc... +SolutionType: TypeAlias = str +ComponentsDirections: TypeAlias = Literal["X", "Y", "Z", "XY", "YZ", "XZ"] + +Nodes: TypeAlias = str | int | Iterable[int | str] | None +Elements: TypeAlias = str | int | Iterable[int | str] | None +MAPDLComponents: TypeAlias = str | Iterable[int | str] | None + +ReturnData: TypeAlias = tuple[ + np.ndarray[Any, np.dtype[np.floating[Any]]], + np.ndarray[Any, np.dtype[np.floating[Any]]], +] +Kwargs: TypeAlias = dict[Any, Any] +P = ParamSpec("P") From 1b02eae30e3c032606646363193b3681b8e8fd98 Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Tue, 5 Aug 2025 10:48:42 +0200 Subject: [PATCH 3/9] refactor: moving methods to data and plotting files. --- src/ansys/mapdl/core/reader/__init__.py | 7 +- src/ansys/mapdl/core/reader/constants.py | 4 + src/ansys/mapdl/core/reader/core.py | 1831 ++-------------------- src/ansys/mapdl/core/reader/data.py | 1366 ++++++++++++++++ src/ansys/mapdl/core/reader/plotting.py | 119 ++ 5 files changed, 1665 insertions(+), 1662 deletions(-) create mode 100644 src/ansys/mapdl/core/reader/data.py create mode 100644 src/ansys/mapdl/core/reader/plotting.py diff --git a/src/ansys/mapdl/core/reader/__init__.py b/src/ansys/mapdl/core/reader/__init__.py index 6b8181749e2..e55a4fa2c2f 100644 --- a/src/ansys/mapdl/core/reader/__init__.py +++ b/src/ansys/mapdl/core/reader/__init__.py @@ -20,4 +20,9 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from ansys.mapdl.core.reader.core import DPFResult +from ansys.mapdl.core.reader.data import DPFResultData +from ansys.mapdl.core.reader.plotting import DPFResultPlotting + + +class DPFResult(DPFResultData, DPFResultPlotting): + """Unified class for accessing DPF result data and plotting""" diff --git a/src/ansys/mapdl/core/reader/constants.py b/src/ansys/mapdl/core/reader/constants.py index a352b81fc30..9bb5d6e74c6 100644 --- a/src/ansys/mapdl/core/reader/constants.py +++ b/src/ansys/mapdl/core/reader/constants.py @@ -90,3 +90,7 @@ "YZIT", "YZIC", ] + + +NOT_AVAILABLE_METHOD = """The method '{method}' has not been ported to the new DPF-based Results backend. +If you still want to use it, you can switch to 'pymapdl-reader' backend.""" diff --git a/src/ansys/mapdl/core/reader/core.py b/src/ansys/mapdl/core/reader/core.py index 735100b8012..399d7ed0152 100644 --- a/src/ansys/mapdl/core/reader/core.py +++ b/src/ansys/mapdl/core/reader/core.py @@ -52,27 +52,21 @@ from ansys.mapdl.core.reader.constants import ( LOCATION_MAPPING, MATERIAL_PROPERTIES, + NOT_AVAILABLE_METHOD, ) from ansys.mapdl.core.reader.types import ( - ComponentsDirections, Elements, Entities, EntityType, Ids, - Kwargs, Locations, - MAPDLComponents, Nodes, P, ResultField, ReturnData, Rnum, - SolutionType, ) -NOT_AVAILABLE_METHOD = """The method '{method}' has not been ported to the new DPF-based Results backend. -If you still want to use it, you can switch to 'pymapdl-reader' backend.""" - class ResultNotFound(MapdlRuntimeError): """Exception raised when a result is not found. @@ -120,7 +114,7 @@ def wrapper(self, *args: P.args, **kwargs: P.kwargs): return wrapper -class DPFResult: +class DPFResultCore: """ Result object based on DPF library. @@ -1192,1221 +1186,235 @@ def _get_result( return op if return_operator else self._extract_data(op) - def nodal_displacement( - self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None - ) -> ReturnData: - """Returns the DOF solution for each node in the global - cartesian coordinate system or nodal coordinate system. - - Parameters - ---------- - rnum - Cumulative result number with zero based indexing, or a - list containing (step, substep) of the requested result. - - in_nodal_coord_sys - When ``True``, returns results in the nodal coordinate - system. Default ``False``. - - nodes - Select a limited subset of nodes. Can be a nodal - component or array of node numbers. For example - - * ``"MY_COMPONENT"`` - * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` - * ``np.arange(1000, 2001)`` + @property + def n_results(self) -> int: + """Number of results. Returns ------- - int np.ndarray - Node numbers associated with the results. + int + The number of results. + """ + return self.model.metadata.result_info.n_results - float np.ndarray - Array of nodal displacements. Array - is (``nnod`` x ``sumdof``), the number of nodes by the - number of degrees of freedom which includes ``numdof`` and - ``nfldof`` + @property + def filename(self) -> str: + """String form of the filename. - Examples - -------- - Return the nodal solution (in this case, displacement) for the - first result of ``"file.rst"`` + This property can not be changed. - >>> from ansys.mapdl.core.reader import DPFResult as Result - >>> rst = Result('file.rst') - >>> nnum, data = rst.nodal_solution(0) + Returns + ------- + str + The string form of the filename. + """ + return self._rst # in the reader, this contains the complete path. - Return the nodal solution just for the nodal component - ``'MY_COMPONENT'``. + @property + def pathlib_filename(self) -> pathlib.Path: + """Return the ``pathlib.Path`` version of the filename. - >>> nnum, data = rst.nodal_solution(0, nodes='MY_COMPONENT') + This property can not be changed. - Return the nodal solution just for the nodes from 20 through 50. + Returns + ------- + pathlib.Path + The ``pathlib.Path`` version of the filename. + """ + return pathlib.Path(self._rst) - >>> nnum, data = rst.nodal_solution(0, nodes=range(20, 51)) + @property + def nsets(self) -> int: + """Number of result sets. - Notes - ----- - Some solution results may not include results for each node. - These results are removed by and the node numbers of the - solution results are reflected in ``nnum``. + Returns + ------- + int + The number of result sets. """ - return self._get_nodes_result(rnum, "displacement", in_nodal_coord_sys, nodes) - - def nodal_solution( - self, - rnum: Rnum, - in_nodal_coord_sys: bool = False, - nodes: Nodes = None, - return_temperature: bool = False, - ) -> ReturnData: - """Returns the DOF solution for each node in the global - cartesian coordinate system or nodal coordinate system. + return self.metadata.time_freq_support.n_sets - Solution may be nodal temperatures or nodal displacements - depending on the type of the solution. + def parse_step_substep(self, user_input: int | list[int] | tuple[int, int]) -> int: + """Converts (step, substep) to a cumulative index. Parameters ---------- - rnum - Cumulative result number with zero based indexing, or a - list containing (step, substep) of the requested result. - - in_nodal_coord_sys - When ``True``, returns results in the nodal coordinate - system. Default ``False``. - - nodes - Select a limited subset of nodes. Can be a nodal - component or array of node numbers. For example - - * ``"MY_COMPONENT"`` - * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` - * ``np.arange(1000, 2001)`` - - return_temperature - When ``True``, returns the nodal temperature instead of - the displacement. Default ``False``. + user_input : int | list[int] | tuple[int, int] + The input to convert, either a single step number or a (step, substep) tuple. Returns ------- - int np.ndarray - Node numbers associated with the results. - - float np.ndarray - Array of nodal displacements or nodal temperatures. Array - is (``nnod`` x ``sumdof``), the number of nodes by the - number of degrees of freedom which includes ``numdof`` and - ``nfldof`` - - Examples - -------- - Return the nodal solution (in this case, displacement) for the - first result of ``"file.rst"`` - - >>> from ansys.mapdl.core.reader import DPFResult as Result - >>> rst = Result('file.rst') - >>> nnum, data = rst.nodal_solution(0) - - Return the nodal solution just for the nodal component - ``'MY_COMPONENT'``. - - >>> nnum, data = rst.nodal_solution(0, nodes='MY_COMPONENT') - - Return the nodal solution just for the nodes from 20 through 50. - - >>> nnum, data = rst.nodal_solution(0, nodes=range(20, 51)) - - Notes - ----- - Some solution results may not include results for each node. - These results are removed by and the node numbers of the - solution results are reflected in ``nnum``. + int + The cumulative index corresponding to the input. """ + if isinstance(user_input, int): + return self.metadata.time_freq_support.get_cumulative_index( + user_input + ) # 0 based indexing - if hasattr(self.model.results, "displacement") and not return_temperature: - return self.nodal_displacement(rnum, in_nodal_coord_sys, nodes) - elif hasattr(self.model.results, "temperature"): - return self.nodal_temperature(rnum, nodes) - else: - raise ResultNotFound( - "The current analysis does not have 'displacement' or 'temperature' results." + elif isinstance(user_input, (list, tuple)): + return self.metadata.time_freq_support.get_cumulative_index( + user_input[0], user_input[1] ) - def nodal_temperature(self, rnum: Rnum, nodes: Nodes = None) -> ReturnData: - """Retrieves the temperature for each node in the - solution. - - The order of the results corresponds to the sorted node - numbering. - - Equivalent MAPDL command: PRNSOL, TEMP - - Parameters - ---------- - rnum : int or list - Cumulative result number with zero based indexing, or a - list containing (step, substep) of the requested result. - - nodes : str, sequence of int or str, optional - Select a limited subset of nodes. Can be a nodal - component or array of node numbers. For example + else: + raise TypeError("Input must be either an int or a list") - * ``"MY_COMPONENT"`` - * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` - * ``np.arange(1000, 2001)`` + @property + def version(self) -> float: + """The version of MAPDL used to generate this result file. Returns ------- - nnum : numpy.ndarray - Node numbers of the result. - - temperature : numpy.ndarray - Temperature at each node. + float + The version of MAPDL used to generate this result file. Examples -------- - >>> from ansys.mapdl.core.reader import DPFResult as Result - >>> rst = Result('file.rst') - >>> nnum, temp = rst.nodal_temperature(0) - - Return the temperature just for the nodal component - ``'MY_COMPONENT'``. - - >>> nnum, temp = rst.nodal_stress(0, nodes='MY_COMPONENT') - - Return the temperature just for the nodes from 20 through 50. - - >>> nnum, temp = rst.nodal_solution(0, nodes=range(20, 51)) + >>> mapdl.result.version + 20.1 """ - return self._get_nodes_result(rnum, "temperature", nodes=nodes) - - def nodal_voltage( - self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None - ) -> ReturnData: - """Retrieves the voltage for each node in the - solution. - - The order of the results corresponds to the sorted node - numbering. + return float(self.model.metadata.result_info.solver_version) - Equivalent MAPDL command: PRNSOL, VOLT + @property + def n_sector(self) -> int | None: + """Number of sectors. - Parameters - ---------- - rnum - Cumulative result number with zero based indexing, or a - list containing (step, substep) of the requested result. + Returns + ------- + int | None + The number of sectors, or None if not applicable. + """ + if self.model.metadata.result_info.has_cyclic: + return self.model.metadata.result_info.cyclic_support.num_sectors() - in_nodal_coord_sys - When ``True``, returns results in the nodal coordinate - system. Default ``False``. + @property + def num_stages(self) -> int | None: + """Number of cyclic stages in the model. - nodes - Select a limited subset of nodes. Can be a nodal - component or array of node numbers. For example + Returns + ------- + int | None + The number of cyclic stages, or None if not applicable. + """ + if self.model.metadata.result_info.has_cyclic: + return self.model.metadata.result_info.cyclic_support.num_stages - * ``"MY_COMPONENT"`` - * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` - * ``np.arange(1000, 2001)`` + @property + def title(self) -> str: + """Title of model in database Returns ------- - numpy.ndarray - Node numbers of the result. + str + The title of the model. + """ + return self.model.metadata.result_info.main_title - numpy.ndarray - Voltage at each node. + @property + def is_cyclic(self) -> bool: + """Indicates if the model is cyclic. - Examples - -------- - >>> from ansys.mapdl.core.reader import DPFResult as Result - >>> rst = Result('file.rst') - >>> nnum, temp = rst.nodal_voltage(0) + Returns + ------- + bool + True if the model is cyclic, False otherwise. + """ + return self.model.metadata.result_info.has_cyclic - Return the voltage just for the nodal component - ``'MY_COMPONENT'``. + @property + def units(self) -> str: + """Units of the model. - >>> nnum, temp = rst.nodal_stress(0, nodes='MY_COMPONENT') + Returns + ------- + str + The unit system name. """ - return self._get_nodes_result( - rnum, "electric_potential", in_nodal_coord_sys, nodes - ) - - def element_stress( - self, - rnum: Rnum, - principal: bool = False, - in_element_coord_sys: bool = False, - elements: Elements = None, - **kwargs: Kwargs, - ): - """Retrieves the element component stresses. + return self.model.metadata.result_info.unit_system_name - Equivalent ANSYS command: PRESOL, S + def __repr__(self) -> str: + """Representation of the result object. - Parameters - ---------- - rnum : int or list - Cumulative result number with zero based indexing, or a - list containing (step, substep) of the requested result. + Returns + ------- + str + A string representation of the result object. + """ + if self.is_distributed: + rst_info = ["PyMAPDL Reader Distributed Result"] + else: + rst_info = ["PyMAPDL Result"] - principal : bool, optional - Returns principal stresses instead of component stresses. - Default False. + rst_info.append("{:<12s}: {:s}".format("title".capitalize(), self.title)) + # rst_info.append("{:<12s}: {:s}".format("subtitle".capitalize(), self.subtitle)) #TODO: subtitle is not implemented in DPF. + rst_info.append("{:<12s}: {:s}".format("units".capitalize(), self.units)) - in_element_coord_sys : bool, optional - When ``True``, returns results in the element coordinate - system. Default ``False``. + rst_info.append("{:<12s}: {}".format("Version", self.version)) + rst_info.append("{:<12s}: {}".format("Cyclic", self.is_cyclic)) + rst_info.append("{:<12s}: {:d}".format("Result Sets", self.nsets)) - in_element_coord_sys : bool, optional - Returns the results in the element coordinate system. - Default False and will return the results in the global - coordinate system. + rst_info.append("{:<12s}: {:d}".format("Nodes", self.mesh.nodes.n_nodes)) + rst_info.append( + "{:<12s}: {:d}".format("Elements", self.mesh.elements.n_elements) + ) - elements : str, sequence of int or str, optional - Select a limited subset of elements. Can be a element - component or array of element numbers. For example + rst_info.append("\n") + rst_info.append(self.available_results) + return "\n".join(rst_info) - * ``"MY_COMPONENT"`` - * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` - * ``np.arange(1000, 2001)`` + @property + def available_results(self) -> str: + """Available result types. - **kwargs : optional keyword arguments - Hidden options for distributed result files. + .. versionchanged:: 0.64 + From 0.64, the MAPDL data labels (i.e. NSL for nodal displacements, + ENS for nodal stresses, etc) are not included in the output of this command. Returns ------- - enum : np.ndarray - ANSYS element numbers corresponding to each element. - - element_stress : list - Stresses at each element for each node for Sx Sy Sz Sxy - Syz Sxz or SIGMA1, SIGMA2, SIGMA3, SINT, SEQV when - principal is True. - - enode : list - Node numbers corresponding to each element's stress - results. One list entry for each element. + str + A list of available result types. Examples -------- - Element component stress for the first result set. - - >>> rst.element_stress(0) - - Element principal stress for the first result set. - - >>> enum, element_stress, enode = result.element_stress(0, principal=True) - - Notes - ----- - Shell stresses for element 181 are returned for top and bottom - layers. Results are ordered such that the top layer and then - the bottom layer is reported. + >>> mapdl.result.available_results + Available Results: + Nodal Displacement + Nodal Velocity + Nodal Acceleration + Nodal Force + ElementalNodal Element nodal Forces + ElementalNodal Stress + Elemental Volume + Elemental Energy-stiffness matrix + Elemental Hourglass Energy + Elemental thermal dissipation energy + Elemental Kinetic Energy + Elemental co-energy + Elemental incremental energy + ElementalNodal Strain + ElementalNodal Thermal Strains + ElementalNodal Thermal Strains eqv + ElementalNodal Swelling Strains + ElementalNodal Temperature + Nodal Temperature + ElementalNodal Heat flux + ElementalNodal Heat flux """ - if kwargs: - raise NotImplementedError( - "Hidden options for distributed result files are not implemented." + text = "Available Results:\n" + for each_available_result in self.model.metadata.result_info.available_results: + text += ( # TODO: Missing label data NSL, VSL, etc + each_available_result.native_location + + " " + + each_available_result.physical_name + + "\n" ) + return text - if principal: - op = self._get_elem_result( - rnum, - "stress", - in_element_coord_sys=in_element_coord_sys, - elements=elements, - return_operator=True, - ) - return self._get_principal(op) - return self._get_elem_result(rnum, "stress", in_element_coord_sys, elements) - - def element_nodal_stress( - self, - rnum: Rnum, - principal: bool = False, - in_element_coord_sys: bool = False, - elements: Elements = None, - **kwargs: Kwargs, - ): - """Retrieves the nodal stresses for each element. - - Parameters - ---------- - rnum - Cumulative result number with zero based indexing, or a list containing - (step, substep) of the requested result. - - principal - Returns principal stresses instead of component stresses. - Default False. - - in_element_coord_sys - Returns the results in the element coordinate system if ``True``. - Else, it returns the results in the global coordinate system. - Default False - - elements - Select a limited subset of elements. Can be a element - component or array of element numbers. For example: - - * ``"MY_COMPONENT"`` - * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` - * ``np.arange(1000, 2001)`` - - **kwargs - Hidden options for distributed result files. - - Returns - ------- - np.ndarray - ANSYS element numbers corresponding to each element. - - list - Stresses at each element for each node for Sx Sy Sz Sxy - Syz Sxz or SIGMA1, SIGMA2, SIGMA3, SINT, SEQV when - principal is True. - - list - Node numbers corresponding to each element's stress - results. One list entry for each element. - - Examples - -------- - Element component stress for the first result set. - - >>> rst.element_stress(0) - - Element principal stress for the first result set. - - >>> enum, element_stress, enode = result.element_stress(0, principal=True) - - Notes - ----- - Shell stresses for element 181 are returned for top and bottom - layers. Results are ordered such that the top layer and then - the bottom layer is reported. - """ - if kwargs: - raise NotImplementedError( - "Hidden options for distributed result files are not implemented." - ) - - if principal: - op = self._get_elemnodal_result( - rnum, - "stress", - in_element_coord_sys=in_element_coord_sys, - elements=elements, - return_operator=True, - ) - return self._get_principal(op) - return self._get_elemnodal_result( - rnum, "stress", in_element_coord_sys, elements - ) - - def nodal_elastic_strain( - self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None - ) -> ReturnData: - """Nodal component elastic strains. This record contains - strains in the order ``X, Y, Z, XY, YZ, XZ, EQV``. - - Elastic strains can be can be nodal values extrapolated from - the integration points or values at the integration points - moved to the nodes. - - Equivalent MAPDL command: ``PRNSOL, EPEL`` - - Parameters - ---------- - rnum - Cumulative result number with zero based indexing, or a - list containing (step, substep) of the requested result. - - in_nodal_coord_sys - When ``True``, returns results in the nodal coordinate - system. Default False. - - nodes - Select a limited subset of nodes. Can be a nodal - component or array of node numbers. For example - - * ``"MY_COMPONENT"`` - * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` - * ``np.arange(1000, 2001)`` - - Returns - ------- - np.ndarray - MAPDL node numbers. - - np.ndarray - Nodal component elastic strains. Array is in the order - ``X, Y, Z, XY, YZ, XZ, EQV``. - - .. versionchanged:: 0.64 - The nodes with no values are now equals to zero. - The results of the midnodes are also calculated and - presented. - - Examples - -------- - Load the nodal elastic strain for the first result. - - >>> from ansys.mapdl.core.reader import DPFResult as Result - >>> rst = Result('file.rst') - >>> nnum, elastic_strain = rst.nodal_elastic_strain(0) - - Return the nodal elastic strain just for the nodal component - ``'MY_COMPONENT'``. - - >>> nnum, elastic_strain = rst.nodal_elastic_strain(0, nodes='MY_COMPONENT') - - Return the nodal elastic strain just for the nodes from 20 through 50. - - >>> nnum, elastic_strain = rst.nodal_elastic_strain(0, nodes=range(20, 51)) - - Notes - ----- - Nodes without a strain will be NAN. - - .. - """ - return self._get_nodes_result( - rnum, "elastic_strain", in_nodal_coord_sys=in_nodal_coord_sys, nodes=nodes - ) - - def nodal_plastic_strain( - self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None - ) -> ReturnData: - """Nodal component plastic strains. - - This record contains strains in the order: - ``X, Y, Z, XY, YZ, XZ, EQV``. - - Plastic strains are always values at the integration points - moved to the nodes. - - Parameters - ---------- - rnum - Cumulative result number with zero based indexing, or a - list containing (step, substep) of the requested result. - - in_nodal_coord_sys - When ``True``, returns results in the nodal coordinate - system. Default False. - - nodes - Select a limited subset of nodes. Can be a nodal - component or array of node numbers. For example - - * ``"MY_COMPONENT"`` - * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` - * ``np.arange(1000, 2001)`` - - Returns - ------- - np.ndarray - MAPDL node numbers. - - np.ndarray - Nodal component plastic strains. Array is in the order - ``X, Y, Z, XY, YZ, XZ, EQV``. - - Examples - -------- - Load the nodal plastic strain for the first solution. - - >>> from ansys.mapdl.core.reader import DPFResult as Result - >>> rst = Result('file.rst') - >>> nnum, plastic_strain = rst.nodal_plastic_strain(0) - - Return the nodal plastic strain just for the nodal component - ``'MY_COMPONENT'``. - - >>> nnum, plastic_strain = rst.nodal_plastic_strain(0, nodes='MY_COMPONENT') - - Return the nodal plastic strain just for the nodes from 20 - through 50. - - >>> nnum, plastic_strain = rst.nodal_plastic_strain(0, nodes=range(20, 51)) - """ - return self._get_nodes_result(rnum, "plastic_strain", in_nodal_coord_sys, nodes) - - def nodal_acceleration( - self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None - ) -> ReturnData: - """Nodal velocities for a given result set. - - Parameters - ---------- - rnum - Cumulative result number with zero based indexing, or a - list containing (step, substep) of the requested result. - - in_nodal_coord_sys - When ``True``, returns results in the nodal coordinate - system. Default False. - - nodes - Select a limited subset of nodes. Can be a nodal - component or array of node numbers. For example - - * ``"MY_COMPONENT"`` - * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` - * ``np.arange(1000, 2001)`` - - Returns - ------- - np.ndarray - Node numbers associated with the results. - - np.ndarray - Array of nodal accelerations. Array is (``nnod`` x - ``sumdof``), the number of nodes by the number of degrees - of freedom which includes ``numdof`` and ``nfldof`` - - Examples - -------- - >>> from ansys.mapdl.core.reader import DPFResult as Result - >>> rst = Result('file.rst') - >>> nnum, data = rst.nodal_acceleration(0) - - Notes - ----- - Some solution results may not include results for each node. - These results are removed by and the node numbers of the - solution results are reflected in ``nnum``. - """ - return self._get_nodes_result(rnum, "acceleration", in_nodal_coord_sys, nodes) - - def nodal_reaction_forces( - self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None - ) -> ReturnData: - """Nodal reaction forces. - - Parameters - ---------- - rnum - Cumulative result number with zero based indexing, or a - list containing (step, substep) of the requested result. - - in_nodal_coord_sys - When ``True``, returns results in the nodal coordinate - system. Default False. - - nodes - Select a limited subset of nodes. Can be a nodal - component or array of node numbers. For example - - * ``"MY_COMPONENT"`` - * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` - * ``np.arange(1000, 2001)`` - - Returns - ------- - np.ndarray - Node numbers corresponding to the reaction forces. Node - numbers may be repeated if there is more than one degree - of freedom for each node. - - np.ndarray - Degree of freedom corresponding to each node using the - MAPDL degree of freedom reference table. - - Examples - -------- - Get the nodal reaction forces for the first result and print - the reaction forces of a single node. - - >>> from ansys.mapdl.core.reader import DPFResult as Result - >>> rst = Result('file.rst') - >>> rforces, nnum, dof = rst.nodal_reaction_forces(0) - >>> dof_ref = rst.result_dof(0) - >>> rforces[:3], nnum[:3], dof[:3], dof_ref - (array([ 24102.21376091, -109357.01854005, 22899.5303263 ]), - array([4142, 4142, 4142]), - array([1, 2, 3], dtype=int32), - ['UX', 'UY', 'UZ']) - """ - return self._get_nodes_result(rnum, "reaction_force", in_nodal_coord_sys, nodes) - - def nodal_stress( - self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None - ) -> ReturnData: - """Retrieves the component stresses for each node in the - solution. - - The order of the results corresponds to the sorted node - numbering. - - Computes the nodal stress by averaging the stress for each - element at each node. Due to the discontinuities across - elements, stresses will vary based on the element they are - evaluated from. - - Parameters - ---------- - rnum - Cumulative result number with zero based indexing, or a - list containing (step, substep) of the requested result. - - in_nodal_coord_sys - When ``True``, returns results in the nodal coordinate - system. Default False. - - nodes - Select a limited subset of nodes. Can be a nodal - component or array of node numbers. For example - - * ``"MY_COMPONENT"`` - * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` - * ``np.arange(1000, 2001)`` - - Returns - ------- - nnum : numpy.ndarray - Node numbers of the result. - - stress : numpy.ndarray - Stresses at ``X, Y, Z, XY, YZ, XZ`` averaged at each corner - node. - - Examples - -------- - >>> from ansys.mapdl.core.reader import DPFResult as Result - >>> rst = Result('file.rst') - >>> nnum, stress = rst.nodal_stress(0) - - Return the nodal stress just for the nodal component - ``'MY_COMPONENT'``. - - >>> nnum, stress = rst.nodal_stress(0, nodes='MY_COMPONENT') - - Return the nodal stress just for the nodes from 20 through 50. - - >>> nnum, stress = rst.nodal_solution(0, nodes=range(20, 51)) - - Notes - ----- - Nodes without a stress value will be NAN. - Equivalent ANSYS command: PRNSOL, S - """ - return self._get_nodes_result(rnum, "stress", in_nodal_coord_sys, nodes) - - def nodal_thermal_strain( - self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None - ) -> ReturnData: - """Nodal component thermal strain. - - This record contains strains in the order X, Y, Z, XY, YZ, XZ, - EQV, and eswell (element swelling strain). Thermal strains - are always values at the integration points moved to the - nodes. - - Equivalent MAPDL command: PRNSOL, EPTH, COMP - - Parameters - ---------- - rnum - Cumulative result number with zero based indexing, or a - list containing (step, substep) of the requested result. - - in_nodal_coord_sys - When ``True``, returns results in the nodal coordinate - system. Default False. - - nodes - Select a limited subset of nodes. Can be a nodal - component or array of node numbers. For example - - * ``"MY_COMPONENT"`` - * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` - * ``np.arange(1000, 2001)`` - - Returns - ------- - np.ndarray - MAPDL node numbers. - - np.ndarray - Nodal component plastic strains. Array is in the order - ``X, Y, Z, XY, YZ, XZ, EQV, ESWELL`` - - Examples - -------- - Load the nodal thermal strain for the first solution. - - >>> from ansys.mapdl.core.reader import DPFResult as Result - >>> rst = Result('file.rst') - >>> nnum, thermal_strain = rst.nodal_thermal_strain(0) - - Return the nodal thermal strain just for the nodal component - ``'MY_COMPONENT'``. - - >>> nnum, thermal_strain = rst.nodal_thermal_strain(0, nodes='MY_COMPONENT') - - Return the nodal thermal strain just for the nodes from 20 through 50. - - >>> nnum, thermal_strain = rst.nodal_thermal_strain(0, nodes=range(20, 51)) - """ - return self._get_nodes_result(rnum, "thermal_strain", in_nodal_coord_sys, nodes) - - def nodal_velocity( - self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None - ) -> ReturnData: - """Nodal velocities for a given result set. - - Parameters - ---------- - rnum - Cumulative result number with zero based indexing, or a - list containing (step, substep) of the requested result. - - in_nodal_coord_sys - When ``True``, returns results in the nodal coordinate - system. Default False. - - nodes - Select a limited subset of nodes. Can be a nodal - component or array of node numbers. For example - - * ``"MY_COMPONENT"`` - * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` - * ``np.arange(1000, 2001)`` - - Returns - ------- - nnum : int np.ndarray - Node numbers associated with the results. - - result : float np.ndarray - Array of nodal velocities. Array is (``nnod`` x - ``sumdof``), the number of nodes by the number of degrees - of freedom which includes ``numdof`` and ``nfldof`` - - Examples - -------- - >>> from ansys.mapdl.core.reader import DPFResult as Result - >>> rst = Result('file.rst') - >>> nnum, data = rst.nodal_velocity(0) - - Notes - ----- - Some solution results may not include results for each node. - These results are removed by and the node numbers of the - solution results are reflected in ``nnum``. - """ - return self._get_nodes_result(rnum, "velocity", in_nodal_coord_sys, nodes) - - def nodal_static_forces( - self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None - ) -> ReturnData: - """Return the nodal forces averaged at the nodes. - - Nodal forces are computed on an element by element basis, and - this method averages the nodal forces for each element for - each node. - - Parameters - ---------- - rnum - Cumulative result number with zero based indexing, or a - list containing (step, substep) of the requested result. - - in_nodal_coord_sys - When ``True``, returns results in the nodal coordinate - system. Default False. - - nodes - Select a limited subset of nodes. Can be a nodal - component or array of node numbers. For example - - * ``"MY_COMPONENT"`` - * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` - * ``np.arange(1000, 2001)`` - - Returns - ------- - np.ndarray - MAPDL node numbers. - - np.ndarray - Averaged nodal forces. Array is sized ``[nnod x numdof]`` - where ``nnod`` is the number of nodes and ``numdof`` is the - number of degrees of freedom for this solution. - - Examples - -------- - Load the nodal static forces for the first result using the - example hexahedral result file. - - >>> from ansys.mapdl import reader as pymapdl_reader - >>> from ansys.mapdl.reader import examples - >>> rst = pymapdl_reader.read_binary(examples.rstfile) - >>> nnum, forces = rst.nodal_static_forces(0) - - Return the nodal static forces just for the nodal component - ``'MY_COMPONENT'``. - - >>> nnum, forces = rst.nodal_static_forces(0, nodes='MY_COMPONENT') - - Return the nodal static forces just for the nodes from 20 through 50. - - >>> nnum, forces = rst.nodal_static_forces(0, nodes=range(20, 51)) - - Notes - ----- - Nodes without a a nodal will be NAN. These are generally - midside (quadratic) nodes. - """ - return self._get_nodes_result(rnum, "nodal_force", in_nodal_coord_sys, nodes) - - def principal_nodal_stress( - self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None - ) -> ReturnData: - """Computes the principal component stresses for each node in - the solution. - - Parameters - ---------- - rnum - Cumulative result number with zero based indexing, or a - list containing (step, substep) of the requested result. - - in_nodal_coord_sys - If True, return the results in the nodal coordinate system. - - nodes - Select a limited subset of nodes. Can be a nodal - component or array of node numbers. For example - - * ``"MY_COMPONENT"`` - * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` - * ``np.arange(1000, 2001)`` - - Returns - ------- - numpy.ndarray - Node numbers of the result. - - numpy.ndarray - Principal stresses, stress intensity, and equivalent stress. - [sigma1, sigma2, sigma3, sint, seqv] - - Examples - -------- - Load the principal nodal stress for the first solution. - - >>> from ansys.mapdl.core.reader import DPFResult as Result - >>> rst = Result('file.rst') - >>> nnum, stress = rst.principal_nodal_stress(0) - - Notes - ----- - ANSYS equivalent of: - PRNSOL, S, PRIN - - which returns: - S1, S2, S3 principal stresses, SINT stress intensity, and SEQV - equivalent stress. - - Internal averaging algorithm averages the component values - from the elements at a common node and then calculates the - principal using the averaged value. - - See the MAPDL ``AVPRIN`` command for more details. - ``ansys-mapdl-reader`` uses the default ``AVPRIN, 0`` option. - """ - op = self._get_nodes_result( - rnum, - "stress", - in_nodal_coord_sys=in_nodal_coord_sys, - nodes=nodes, - return_operator=True, - ) - return self._get_principal(op) - - @property - def n_results(self) -> int: - """Number of results. - - Returns - ------- - int - The number of results. - """ - return self.model.metadata.result_info.n_results - - @property - def filename(self) -> str: - """String form of the filename. - - This property can not be changed. - - Returns - ------- - str - The string form of the filename. - """ - return self._rst # in the reader, this contains the complete path. - - @property - def pathlib_filename(self) -> pathlib.Path: - """Return the ``pathlib.Path`` version of the filename. - - This property can not be changed. - - Returns - ------- - pathlib.Path - The ``pathlib.Path`` version of the filename. - """ - return pathlib.Path(self._rst) - - @property - def nsets(self) -> int: - """Number of result sets. - - Returns - ------- - int - The number of result sets. - """ - return self.metadata.time_freq_support.n_sets - - def parse_step_substep(self, user_input: int | list[int] | tuple[int, int]) -> int: - """Converts (step, substep) to a cumulative index. - - Parameters - ---------- - user_input : int | list[int] | tuple[int, int] - The input to convert, either a single step number or a (step, substep) tuple. - - Returns - ------- - int - The cumulative index corresponding to the input. - """ - if isinstance(user_input, int): - return self.metadata.time_freq_support.get_cumulative_index( - user_input - ) # 0 based indexing - - elif isinstance(user_input, (list, tuple)): - return self.metadata.time_freq_support.get_cumulative_index( - user_input[0], user_input[1] - ) - - else: - raise TypeError("Input must be either an int or a list") - - @property - def version(self) -> float: - """The version of MAPDL used to generate this result file. - - Returns - ------- - float - The version of MAPDL used to generate this result file. - - Examples - -------- - >>> mapdl.result.version - 20.1 - """ - return float(self.model.metadata.result_info.solver_version) - - @property - def available_results(self) -> str: - """Available result types. - - .. versionchanged:: 0.64 - From 0.64, the MAPDL data labels (i.e. NSL for nodal displacements, - ENS for nodal stresses, etc) are not included in the output of this command. - - Returns - ------- - str - A list of available result types. - - Examples - -------- - >>> mapdl.result.available_results - Available Results: - Nodal Displacement - Nodal Velocity - Nodal Acceleration - Nodal Force - ElementalNodal Element nodal Forces - ElementalNodal Stress - Elemental Volume - Elemental Energy-stiffness matrix - Elemental Hourglass Energy - Elemental thermal dissipation energy - Elemental Kinetic Energy - Elemental co-energy - Elemental incremental energy - ElementalNodal Strain - ElementalNodal Thermal Strains - ElementalNodal Thermal Strains eqv - ElementalNodal Swelling Strains - ElementalNodal Temperature - Nodal Temperature - ElementalNodal Heat flux - ElementalNodal Heat flux - """ - text = "Available Results:\n" - for each_available_result in self.model.metadata.result_info.available_results: - text += ( # TODO: Missing label data NSL, VSL, etc - each_available_result.native_location - + " " - + each_available_result.physical_name - + "\n" - ) - return text - - @property - def n_sector(self) -> int | None: - """Number of sectors. - - Returns - ------- - int | None - The number of sectors, or None if not applicable. - """ - if self.model.metadata.result_info.has_cyclic: - return self.model.metadata.result_info.cyclic_support.num_sectors() - - @property - def num_stages(self) -> int | None: - """Number of cyclic stages in the model. - - Returns - ------- - int | None - The number of cyclic stages, or None if not applicable. - """ - if self.model.metadata.result_info.has_cyclic: - return self.model.metadata.result_info.cyclic_support.num_stages - - @property - def title(self) -> str: - """Title of model in database - - Returns - ------- - str - The title of the model. - """ - return self.model.metadata.result_info.main_title - - @property - def is_cyclic(self) -> bool: - """Indicates if the model is cyclic. - - Returns - ------- - bool - True if the model is cyclic, False otherwise. - """ - return self.model.metadata.result_info.has_cyclic - - @property - def units(self) -> str: - """Units of the model. - - Returns - ------- - str - The unit system name. - """ - return self.model.metadata.result_info.unit_system_name - - def __repr__(self) -> str: - """Representation of the result object. - - Returns - ------- - str - A string representation of the result object. - """ - if self.is_distributed: - rst_info = ["PyMAPDL Reader Distributed Result"] - else: - rst_info = ["PyMAPDL Result"] - - rst_info.append("{:<12s}: {:s}".format("title".capitalize(), self.title)) - # rst_info.append("{:<12s}: {:s}".format("subtitle".capitalize(), self.subtitle)) #TODO: subtitle is not implemented in DPF. - rst_info.append("{:<12s}: {:s}".format("units".capitalize(), self.units)) - - rst_info.append("{:<12s}: {}".format("Version", self.version)) - rst_info.append("{:<12s}: {}".format("Cyclic", self.is_cyclic)) - rst_info.append("{:<12s}: {:d}".format("Result Sets", self.nsets)) - - rst_info.append("{:<12s}: {:d}".format("Nodes", self.mesh.nodes.n_nodes)) - rst_info.append( - "{:<12s}: {:d}".format("Elements", self.mesh.elements.n_elements) - ) - - rst_info.append("\n") - rst_info.append(self.available_results) - return "\n".join(rst_info) - - def nodal_time_history( - self, solution_type: SolutionType = "NSL", in_nodal_coord_sys: bool = False - ) -> ReturnData: - """The DOF solution for each node for all result sets. - - The nodal results are returned returned in the global - cartesian coordinate system or nodal coordinate system. - - Parameters - ---------- - solution_type - The solution type. Must be either nodal displacements - (``'NSL'``), nodal velocities (``'VEL'``) or nodal - accelerations (``'ACC'``). - Default is ``'NSL'``. - - in_nodal_coord_sys - When ``True``, returns results in the nodal coordinate system. - Default ``False``. - - Returns - ------- - np.ndarray - Node numbers associated with the results. - - np.ndarray - Nodal solution for all result sets. Array is sized - ``rst.nsets x nnod x Sumdof``, which is the number of - time steps by number of nodes by degrees of freedom. - """ - if solution_type == "NSL": - func = self.nodal_solution # type: ignore - elif solution_type == "VEL": - func = self.nodal_velocity # type: ignore - elif solution_type == "ACC": - func = self.nodal_acceleration # type: ignore - else: - raise ValueError( - "Argument 'solution type' must be either 'NSL', " "'VEL', or 'ACC'" - ) - - # size based on the first result - nnum, sol = func(0, in_nodal_coord_sys=in_nodal_coord_sys) - data = np.empty((self.nsets, sol.shape[0], sol.shape[1]), np.float64) - data[0] = sol - for i in range(1, self.nsets): - data[i] = func(i, in_nodal_coord_sys=in_nodal_coord_sys)[1] - - return nnum, data - - @property - def time_values(self) -> list[float]: - """Values for the time/frequency. + @property + def time_values(self) -> list[float]: + """Values for the time/frequency. Returns ------- @@ -2560,90 +1568,6 @@ def materials(self) -> dict[int, dict[str, int | float]]: mats[mat_id][each_label] = data[0] if len(data) == 1 else data return mats - def plot_nodal_stress( - self, - rnum: Rnum, - comp: ComponentsDirections | None = None, - show_displacement: bool = False, - displacement_factor: int = 1, - node_components: MAPDLComponents | None = None, - element_components: MAPDLComponents | None = None, - sel_type_all: bool = True, - treat_nan_as_zero: bool = True, - **kwargs: Kwargs, - ): - """Plots the stresses at each node in the solution. - - Parameters - ---------- - rnum : int or list - Cumulative result number with zero based indexing, or a - list containing (step, substep) of the requested result. - - comp : str, optional - Stress component to display. Available options: - - ``"X"`` - - ``"Y"`` - - ``"Z"`` - - ``"XY"`` - - ``"YZ"`` - - ``"XZ"`` - - show_displacement - If True, displays the displacement along with the stress - plot. Default is False. - - displacement_factor - Factor by which to scale the displacement plot. Default is 1. - - node_components : list, optional - Accepts either a string or a list strings of node - components to plot. For example: - ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` - - element_components : list, optional - Accepts either a string or a list strings of element - components to plot. For example: - ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` - - sel_type_all : bool, optional - If node_components is specified, plots those elements - containing all nodes of the component. Default ``True``. - - treat_nan_as_zero : bool, optional - Treat NAN values (i.e. stresses at midside nodes) as zero - when plotting. - - **kwargs - Additional keyword arguments. See ``help(pyvista.plot)`` - - Returns - ------- - cpos : list - 3 x 3 vtk camera position. - - Examples - -------- - Plot the X component nodal stress while showing displacement. - - >>> rst.plot_nodal_stress(0, comp='x', show_displacement=True) - """ - # if not comp: - # comp = "X" - - # ind = COMPONENTS.index(comp) - - # op = self._get_nodes_result( - # rnum, - # "stress", - # nodes=node_components, - # in_nodal_coord_sys=False, - # return_operator=True, - # ) - # fc = op.outputs.fields_as_fields_container()[0] - - raise NotImplementedError("WIP") - @property def _elements(self): return self.mesh.elements.scoping.ids @@ -2670,254 +1594,6 @@ def element_lookup(self, element_id: int) -> int: return mapping[element_id] - def overwrite_element_solution_record( - self, - data: list[float] | np.ndarray, - rnum: Rnum, - solution_type: str, - element_id: int, - ): - """Overwrite element solution record. - - This method replaces solution data for of an element at a - result index for a given solution type. The number of items - in ``data`` must match the number of items in the record. - - If you are not sure how many records are in a given record, - use ``element_solution_data`` to retrieve all the records for - a given ``solution_type`` and check the number of items in the - record. - - Note: The record being replaced cannot be a compressed record. - If the result file uses compression (default sparse - compression as of 2019R1), you can disable this within MAPDL - with: - ``/FCOMP, RST, 0`` - - Parameters - ---------- - data : list or np.ndarray - Data that will replace the existing records. - - rnum : int - Zero based result number. - - solution_type : str - Element data type to overwrite. - - - EMS: misc. data - - ENF: nodal forces - - ENS: nodal stresses - - ENG: volume and energies - - EGR: nodal gradients - - EEL: elastic strains - - EPL: plastic strains - - ECR: creep strains - - ETH: thermal strains - - EUL: euler angles - - EFX: nodal fluxes - - ELF: local forces - - EMN: misc. non-sum values - - ECD: element current densities - - ENL: nodal nonlinear data - - EHC: calculated heat generations - - EPT: element temperatures - - ESF: element surface stresses - - EDI: diffusion strains - - ETB: ETABLE items - - ECT: contact data - - EXY: integration point locations - - EBA: back stresses - - ESV: state variables - - MNL: material nonlinear record - - element_id : int - Ansys element number (e.g. ``1``) - - Examples - -------- - Overwrite the elastic strain record for element 1 for the - first result with random data. - - >>> from ansys.mapdl import reader as pymapdl_reader - >>> rst = pymapdl_reader.read_binary('file.rst') - >>> data = np.random.random(56) - >>> rst.overwrite_element_solution_data(data, 0, 'EEL', 1) - """ - raise NotImplementedError( - NOT_AVAILABLE_METHOD.format(method="overwrite_element_solution_record") - ) - - def overwrite_element_solution_records( - self, element_data: dict[int, np.ndarray], rnum: Rnum, solution_type: str - ): - """Overwrite element solution record. - - This method replaces solution data for a set of elements at a - result index for a given solution type. The number of items - in ``data`` must match the number of items in the record. - - If you are not sure how many records are in a given record, - use ``element_solution_data`` to retrieve all the records for - a given ``solution_type`` and check the number of items in the - record. - - Note: The record being replaced cannot be a compressed record. - If the result file uses compression (default sparse - compression as of 2019R1), you can disable this within MAPDL - with: - ``/FCOMP, RST, 0`` - - Parameters - ---------- - element_data : dict - Dictionary of results that will replace the existing records. - - rnum : int - Zero based result number. - - solution_type : str - Element data type to overwrite. - - - EMS: misc. data - - ENF: nodal forces - - ENS: nodal stresses - - ENG: volume and energies - - EGR: nodal gradients - - EEL: elastic strains - - EPL: plastic strains - - ECR: creep strains - - ETH: thermal strains - - EUL: euler angles - - EFX: nodal fluxes - - ELF: local forces - - EMN: misc. non-sum values - - ECD: element current densities - - ENL: nodal nonlinear data - - EHC: calculated heat generations - - EPT: element temperatures - - ESF: element surface stresses - - EDI: diffusion strains - - ETB: ETABLE items - - ECT: contact data - - EXY: integration point locations - - EBA: back stresses - - ESV: state variables - - MNL: material nonlinear record - - Examples - -------- - Overwrite the elastic strain record for elements 1 and 2 with - for the first result with random data. - - >>> from ansys.mapdl import reader as pymapdl_reader - >>> rst = pymapdl_reader.read_binary('file.rst') - >>> data = {1: np.random.random(56), - 2: np.random.random(56)} - >>> rst.overwrite_element_solution_data(data, 0, 'EEL') - """ - raise NotImplementedError( - NOT_AVAILABLE_METHOD.format(method="overwrite_element_solution_records") - ) - - def read_record(self, pointer: int, return_bufsize: bool = False): - """Reads a record at a given position. - - Because ANSYS 19.0+ uses compression by default, you must use - this method rather than ``np.fromfile``. - - Parameters - ---------- - pointer : int - ANSYS file position (n words from start of file). A word - is four bytes. - - return_bufsize : bool, optional - Returns the number of words read (includes header and - footer). Useful for determining the new position in the - file after reading a record. - - Returns - ------- - record : np.ndarray - The record read as a ``n x 1`` numpy array. - - bufsize : float, optional - When ``return_bufsize`` is enabled, returns the number of - words read. - """ - raise NotImplementedError(NOT_AVAILABLE_METHOD.format(method="read_record")) - - def text_result_table(self, rnum: Rnum): - """Returns a text result table for plotting. - - Parameters - ---------- - rnum - The result number to retrieve the table for. - """ - raise NotImplementedError( - NOT_AVAILABLE_METHOD.format(method="text_result_table") - ) - - def write_tables(self, filename: str | pathlib.Path): - """Write binary tables to ASCII. Assumes int32 - - Parameters - ---------- - filename : str, pathlib.Path - Filename to write the tables to. - - Examples - -------- - >>> rst.write_tables('tables.txt') - """ - raise NotImplementedError(NOT_AVAILABLE_METHOD.format(method="write_tables")) - - def cs_4x4(self, cs_cord: int, as_vtk_matrix: bool = False): - """Return a 4x4 transformation matrix for a given coordinate system. - - Parameters - ---------- - cs_cord : int - Coordinate system index. - - as_vtk_matrix : bool, default: False - Return the transformation matrix as a ``vtkMatrix4x4``. - - Returns - ------- - np.ndarray | vtk.vtkMatrix4x4 - Matrix or ``vtkMatrix4x4`` depending on the value of ``as_vtk_matrix``. - - Notes - ----- - Values 11 and greater correspond to local coordinate systems - - Examples - -------- - Return the transformation matrix for coordinate system 1. - - >>> tmat = rst.cs_4x4(1) - >>> tmat - array([[1., 0., 0., 0.], - [0., 1., 0., 0.], - [0., 0., 1., 0.], - [0., 0., 0., 1.]]) - - Return the transformation matrix for coordinate system 5. This - corresponds to ``CSYS, 5``, the cylindrical with global Cartesian Y as - the axis of rotation. - - >>> tmat = rst.cs_4x4(5) - >>> tmat - array([[ 1., 0., 0., 0.], - [ 0., 0., -1., 0.], - [ 0., 1., 0., 0.], - [ 0., 0., 0., 1.]]) - """ - raise NotImplementedError(NOT_AVAILABLE_METHOD.format(method="cs_4x4")) - def solution_info(self, rnum: Rnum): """Return an informative dictionary of solution data for a result. @@ -3076,170 +1752,3 @@ def element_components(self) -> dict[str, np.ndarray]: 16, 17, 18, 19, 20], dtype=int32)} """ return self._get_comp_dict("ELEM") - - def element_solution_data( - self, rnum: Rnum, datatype: str, sort: bool = True, **kwargs: Kwargs - ): - """Retrieves element solution data. Similar to ETABLE. - - Parameters - ---------- - rnum : int or list - Cumulative result number with zero based indexing, or a - list containing (step, substep) of the requested result. - - datatype : str - Element data type to retrieve. - - - EMS: misc. data - - ENF: nodal forces - - ENS: nodal stresses - - ENG: volume and energies - - EGR: nodal gradients - - EEL: elastic strains - - EPL: plastic strains - - ECR: creep strains - - ETH: thermal strains - - EUL: euler angles - - EFX: nodal fluxes - - ELF: local forces - - EMN: misc. non-sum values - - ECD: element current densities - - ENL: nodal nonlinear data - - EHC: calculated heat generations - - EPT: element temperatures - - ESF: element surface stresses - - EDI: diffusion strains - - ETB: ETABLE items - - ECT: contact data - - EXY: integration point locations - - EBA: back stresses - - ESV: state variables - - MNL: material nonlinear record - - sort : bool - Sort results by element number. Default ``True``. - - **kwargs : optional keyword arguments - Hidden options for distributed result files. - - Returns - ------- - enum : np.ndarray - Element numbers. - - element_data : list - List with one data item for each element. - - enode : list - Node numbers corresponding to each element. - results. One list entry for each element. - - Notes - ----- - See ANSYS element documentation for available items for each - element type. See: - - https://www.mm.bme.hu/~gyebro/files/ans_help_v182/ans_elem/ - - Examples - -------- - Retrieve "LS" solution results from an PIPE59 element for result set 1 - - >>> enum, edata, enode = result.element_solution_data(0, datatype='ENS') - >>> enum[0] # first element number - >>> enode[0] # nodes belonging to element 1 - >>> edata[0] # data belonging to element 1 - array([ -4266.19 , -376.18857, -8161.785 , -64706.766 , - -4266.19 , -376.18857, -8161.785 , -45754.594 , - -4266.19 , -376.18857, -8161.785 , 0. , - -4266.19 , -376.18857, -8161.785 , 45754.594 , - -4266.19 , -376.18857, -8161.785 , 64706.766 , - -4266.19 , -376.18857, -8161.785 , 45754.594 , - -4266.19 , -376.18857, -8161.785 , 0. , - -4266.19 , -376.18857, -8161.785 , -45754.594 , - -4274.038 , -376.62527, -8171.2603 , 2202.7085 , - -29566.24 , -376.62527, -8171.2603 , 1557.55 , - -40042.613 , -376.62527, -8171.2603 , 0. , - -29566.24 , -376.62527, -8171.2603 , -1557.55 , - -4274.038 , -376.62527, -8171.2603 , -2202.7085 , - 21018.164 , -376.62527, -8171.2603 , -1557.55 , - 31494.537 , -376.62527, -8171.2603 , 0. , - 21018.164 , -376.62527, -8171.2603 , 1557.55 ], - dtype=float32) - - This data corresponds to the results you would obtain directly - from MAPDL with ESOL commands: - - >>> ansys.esol(nvar='2', elem=enum[0], node=enode[0][0], item='LS', comp=1) - >>> ansys.vget(par='SD_LOC1', ir='2', tstrt='1') # store in a variable - >>> ansys.read_float_parameter('SD_LOC1(1)') - -4266.19 - """ - raise NotImplementedError( - NOT_AVAILABLE_METHOD.format(method="element_solution_data") - ) - - def result_dof(self, rnum: Rnum): - """Return a list of degrees of freedom for a given result number. - - Parameters - ---------- - rnum : int or list - Cumulative result number with zero based indexing, or a - list containing (step, substep) of the requested result. - - Returns - ------- - dof : list - List of degrees of freedom. - - Examples - -------- - >>> rst.result_dof(0) - ['UX', 'UY', 'UZ'] - """ - # To be done later - raise NotImplementedError(NOT_AVAILABLE_METHOD.format(method="result_dof")) - - def nodal_input_force(self, rnum: Rnum): - """Nodal input force for a given result number. - - Nodal input force is generally set with the APDL command - ``F``. For example, ``F, 25, FX, 0.001`` - - Parameters - ---------- - rnum : int or list - Cumulative result number with zero based indexing, or a - list containing (step, substep) of the requested result. - - Returns - ------- - nnum : np.ndarray - Node numbers of the nodes with nodal forces. - - dof : np.ndarray - Array of indices of the degrees of freedom of the nodes - with input force. See ``rst.result_dof`` for the degrees - of freedom associated with each index. - - force : np.ndarray - Nodal input force. - - Examples - -------- - Print the nodal input force where: - - Node 25 has FX=20 - - Node 26 has FY=30 - - Node 27 has FZ=40 - - >>> rst.nodal_input_force(0) - (array([ 25, 26, 27], dtype=int32), - array([2, 1, 3], dtype=int32), - array([30., 20., 40.])) - """ - # To be done later - raise NotImplementedError( - NOT_AVAILABLE_METHOD.format(method="nodal_input_force") - ) diff --git a/src/ansys/mapdl/core/reader/data.py b/src/ansys/mapdl/core/reader/data.py new file mode 100644 index 00000000000..7792b439ced --- /dev/null +++ b/src/ansys/mapdl/core/reader/data.py @@ -0,0 +1,1366 @@ +# Copyright (C) 2016 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +Classes and functions for returning/manipulating result data +""" +from pathlib import Path + +import numpy as np + +from ansys.mapdl.core.reader.constants import NOT_AVAILABLE_METHOD +from ansys.mapdl.core.reader.core import DPFResultCore, ResultNotFound +from ansys.mapdl.core.reader.types import ( + Elements, + Kwargs, + Nodes, + ReturnData, + Rnum, + SolutionType, +) + + +class DPFResultData(DPFResultCore): + """Provides methods for accessing and manipulating DPF result data.""" + + def nodal_input_force(self, rnum: Rnum): + """Nodal input force for a given result number. + + Nodal input force is generally set with the APDL command + ``F``. For example, ``F, 25, FX, 0.001`` + + Parameters + ---------- + rnum : int or list + Cumulative result number with zero based indexing, or a + list containing (step, substep) of the requested result. + + Returns + ------- + nnum : np.ndarray + Node numbers of the nodes with nodal forces. + + dof : np.ndarray + Array of indices of the degrees of freedom of the nodes + with input force. See ``rst.result_dof`` for the degrees + of freedom associated with each index. + + force : np.ndarray + Nodal input force. + + Examples + -------- + Print the nodal input force where: + - Node 25 has FX=20 + - Node 26 has FY=30 + - Node 27 has FZ=40 + + >>> rst.nodal_input_force(0) + (array([ 25, 26, 27], dtype=int32), + array([2, 1, 3], dtype=int32), + array([30., 20., 40.])) + """ + # To be done later + raise NotImplementedError( + NOT_AVAILABLE_METHOD.format(method="nodal_input_force") + ) + + def element_solution_data( + self, rnum: Rnum, datatype: str, sort: bool = True, **kwargs: Kwargs + ): + """Retrieves element solution data. Similar to ETABLE. + + Parameters + ---------- + rnum : int or list + Cumulative result number with zero based indexing, or a + list containing (step, substep) of the requested result. + + datatype : str + Element data type to retrieve. + + - EMS: misc. data + - ENF: nodal forces + - ENS: nodal stresses + - ENG: volume and energies + - EGR: nodal gradients + - EEL: elastic strains + - EPL: plastic strains + - ECR: creep strains + - ETH: thermal strains + - EUL: euler angles + - EFX: nodal fluxes + - ELF: local forces + - EMN: misc. non-sum values + - ECD: element current densities + - ENL: nodal nonlinear data + - EHC: calculated heat generations + - EPT: element temperatures + - ESF: element surface stresses + - EDI: diffusion strains + - ETB: ETABLE items + - ECT: contact data + - EXY: integration point locations + - EBA: back stresses + - ESV: state variables + - MNL: material nonlinear record + + sort : bool + Sort results by element number. Default ``True``. + + **kwargs : optional keyword arguments + Hidden options for distributed result files. + + Returns + ------- + enum : np.ndarray + Element numbers. + + element_data : list + List with one data item for each element. + + enode : list + Node numbers corresponding to each element. + results. One list entry for each element. + + Notes + ----- + See ANSYS element documentation for available items for each + element type. See: + + https://www.mm.bme.hu/~gyebro/files/ans_help_v182/ans_elem/ + + Examples + -------- + Retrieve "LS" solution results from an PIPE59 element for result set 1 + + >>> enum, edata, enode = result.element_solution_data(0, datatype='ENS') + >>> enum[0] # first element number + >>> enode[0] # nodes belonging to element 1 + >>> edata[0] # data belonging to element 1 + array([ -4266.19 , -376.18857, -8161.785 , -64706.766 , + -4266.19 , -376.18857, -8161.785 , -45754.594 , + -4266.19 , -376.18857, -8161.785 , 0. , + -4266.19 , -376.18857, -8161.785 , 45754.594 , + -4266.19 , -376.18857, -8161.785 , 64706.766 , + -4266.19 , -376.18857, -8161.785 , 45754.594 , + -4266.19 , -376.18857, -8161.785 , 0. , + -4266.19 , -376.18857, -8161.785 , -45754.594 , + -4274.038 , -376.62527, -8171.2603 , 2202.7085 , + -29566.24 , -376.62527, -8171.2603 , 1557.55 , + -40042.613 , -376.62527, -8171.2603 , 0. , + -29566.24 , -376.62527, -8171.2603 , -1557.55 , + -4274.038 , -376.62527, -8171.2603 , -2202.7085 , + 21018.164 , -376.62527, -8171.2603 , -1557.55 , + 31494.537 , -376.62527, -8171.2603 , 0. , + 21018.164 , -376.62527, -8171.2603 , 1557.55 ], + dtype=float32) + + This data corresponds to the results you would obtain directly + from MAPDL with ESOL commands: + + >>> ansys.esol(nvar='2', elem=enum[0], node=enode[0][0], item='LS', comp=1) + >>> ansys.vget(par='SD_LOC1', ir='2', tstrt='1') # store in a variable + >>> ansys.read_float_parameter('SD_LOC1(1)') + -4266.19 + """ + raise NotImplementedError( + NOT_AVAILABLE_METHOD.format(method="element_solution_data") + ) + + def result_dof(self, rnum: Rnum): + """Return a list of degrees of freedom for a given result number. + + Parameters + ---------- + rnum : int or list + Cumulative result number with zero based indexing, or a + list containing (step, substep) of the requested result. + + Returns + ------- + dof : list + List of degrees of freedom. + + Examples + -------- + >>> rst.result_dof(0) + ['UX', 'UY', 'UZ'] + """ + # To be done later + raise NotImplementedError(NOT_AVAILABLE_METHOD.format(method="result_dof")) + + def cs_4x4(self, cs_cord: int, as_vtk_matrix: bool = False): + """Return a 4x4 transformation matrix for a given coordinate system. + + Parameters + ---------- + cs_cord : int + Coordinate system index. + + as_vtk_matrix : bool, default: False + Return the transformation matrix as a ``vtkMatrix4x4``. + + Returns + ------- + np.ndarray | vtk.vtkMatrix4x4 + Matrix or ``vtkMatrix4x4`` depending on the value of ``as_vtk_matrix``. + + Notes + ----- + Values 11 and greater correspond to local coordinate systems + + Examples + -------- + Return the transformation matrix for coordinate system 1. + + >>> tmat = rst.cs_4x4(1) + >>> tmat + array([[1., 0., 0., 0.], + [0., 1., 0., 0.], + [0., 0., 1., 0.], + [0., 0., 0., 1.]]) + + Return the transformation matrix for coordinate system 5. This + corresponds to ``CSYS, 5``, the cylindrical with global Cartesian Y as + the axis of rotation. + + >>> tmat = rst.cs_4x4(5) + >>> tmat + array([[ 1., 0., 0., 0.], + [ 0., 0., -1., 0.], + [ 0., 1., 0., 0.], + [ 0., 0., 0., 1.]]) + """ + raise NotImplementedError(NOT_AVAILABLE_METHOD.format(method="cs_4x4")) + + def read_record(self, pointer: int, return_bufsize: bool = False): + """Reads a record at a given position. + + Because ANSYS 19.0+ uses compression by default, you must use + this method rather than ``np.fromfile``. + + Parameters + ---------- + pointer : int + ANSYS file position (n words from start of file). A word + is four bytes. + + return_bufsize : bool, optional + Returns the number of words read (includes header and + footer). Useful for determining the new position in the + file after reading a record. + + Returns + ------- + record : np.ndarray + The record read as a ``n x 1`` numpy array. + + bufsize : float, optional + When ``return_bufsize`` is enabled, returns the number of + words read. + """ + raise NotImplementedError(NOT_AVAILABLE_METHOD.format(method="read_record")) + + def text_result_table(self, rnum: Rnum): + """Returns a text result table for plotting. + + Parameters + ---------- + rnum + The result number to retrieve the table for. + """ + raise NotImplementedError( + NOT_AVAILABLE_METHOD.format(method="text_result_table") + ) + + def write_tables(self, filename: str | Path): + """Write binary tables to ASCII. Assumes int32 + + Parameters + ---------- + filename : str, Path + Filename to write the tables to. + + Examples + -------- + >>> rst.write_tables('tables.txt') + """ + raise NotImplementedError(NOT_AVAILABLE_METHOD.format(method="write_tables")) + + def overwrite_element_solution_records( + self, element_data: dict[int, np.ndarray], rnum: Rnum, solution_type: str + ): + """Overwrite element solution record. + + This method replaces solution data for a set of elements at a + result index for a given solution type. The number of items + in ``data`` must match the number of items in the record. + + If you are not sure how many records are in a given record, + use ``element_solution_data`` to retrieve all the records for + a given ``solution_type`` and check the number of items in the + record. + + Note: The record being replaced cannot be a compressed record. + If the result file uses compression (default sparse + compression as of 2019R1), you can disable this within MAPDL + with: + ``/FCOMP, RST, 0`` + + Parameters + ---------- + element_data : dict + Dictionary of results that will replace the existing records. + + rnum : int + Zero based result number. + + solution_type : str + Element data type to overwrite. + + - EMS: misc. data + - ENF: nodal forces + - ENS: nodal stresses + - ENG: volume and energies + - EGR: nodal gradients + - EEL: elastic strains + - EPL: plastic strains + - ECR: creep strains + - ETH: thermal strains + - EUL: euler angles + - EFX: nodal fluxes + - ELF: local forces + - EMN: misc. non-sum values + - ECD: element current densities + - ENL: nodal nonlinear data + - EHC: calculated heat generations + - EPT: element temperatures + - ESF: element surface stresses + - EDI: diffusion strains + - ETB: ETABLE items + - ECT: contact data + - EXY: integration point locations + - EBA: back stresses + - ESV: state variables + - MNL: material nonlinear record + + Examples + -------- + Overwrite the elastic strain record for elements 1 and 2 with + for the first result with random data. + + >>> from ansys.mapdl import reader as pymapdl_reader + >>> rst = pymapdl_reader.read_binary('file.rst') + >>> data = {1: np.random.random(56), + 2: np.random.random(56)} + >>> rst.overwrite_element_solution_data(data, 0, 'EEL') + """ + raise NotImplementedError( + NOT_AVAILABLE_METHOD.format(method="overwrite_element_solution_records") + ) + + def nodal_time_history( + self, solution_type: SolutionType = "NSL", in_nodal_coord_sys: bool = False + ) -> ReturnData: + """The DOF solution for each node for all result sets. + + The nodal results are returned returned in the global + cartesian coordinate system or nodal coordinate system. + + Parameters + ---------- + solution_type + The solution type. Must be either nodal displacements + (``'NSL'``), nodal velocities (``'VEL'``) or nodal + accelerations (``'ACC'``). + Default is ``'NSL'``. + + in_nodal_coord_sys + When ``True``, returns results in the nodal coordinate system. + Default ``False``. + + Returns + ------- + np.ndarray + Node numbers associated with the results. + + np.ndarray + Nodal solution for all result sets. Array is sized + ``rst.nsets x nnod x Sumdof``, which is the number of + time steps by number of nodes by degrees of freedom. + """ + if solution_type == "NSL": + func = self.nodal_solution # type: ignore + elif solution_type == "VEL": + func = self.nodal_velocity # type: ignore + elif solution_type == "ACC": + func = self.nodal_acceleration # type: ignore + else: + raise ValueError( + "Argument 'solution type' must be either 'NSL', " "'VEL', or 'ACC'" + ) + + # size based on the first result + nnum, sol = func(0, in_nodal_coord_sys=in_nodal_coord_sys) + data = np.empty((self.nsets, sol.shape[0], sol.shape[1]), np.float64) + data[0] = sol + for i in range(1, self.nsets): + data[i] = func(i, in_nodal_coord_sys=in_nodal_coord_sys)[1] + + return nnum, data + + def nodal_displacement( + self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None + ) -> ReturnData: + """Returns the DOF solution for each node in the global + cartesian coordinate system or nodal coordinate system. + + Parameters + ---------- + rnum + Cumulative result number with zero based indexing, or a + list containing (step, substep) of the requested result. + + in_nodal_coord_sys + When ``True``, returns results in the nodal coordinate + system. Default ``False``. + + nodes + Select a limited subset of nodes. Can be a nodal + component or array of node numbers. For example + + * ``"MY_COMPONENT"`` + * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` + * ``np.arange(1000, 2001)`` + + Returns + ------- + int np.ndarray + Node numbers associated with the results. + + float np.ndarray + Array of nodal displacements. Array + is (``nnod`` x ``sumdof``), the number of nodes by the + number of degrees of freedom which includes ``numdof`` and + ``nfldof`` + + Examples + -------- + Return the nodal solution (in this case, displacement) for the + first result of ``"file.rst"`` + + >>> from ansys.mapdl.core.reader import DPFResult as Result + >>> rst = Result('file.rst') + >>> nnum, data = rst.nodal_solution(0) + + Return the nodal solution just for the nodal component + ``'MY_COMPONENT'``. + + >>> nnum, data = rst.nodal_solution(0, nodes='MY_COMPONENT') + + Return the nodal solution just for the nodes from 20 through 50. + + >>> nnum, data = rst.nodal_solution(0, nodes=range(20, 51)) + + Notes + ----- + Some solution results may not include results for each node. + These results are removed by and the node numbers of the + solution results are reflected in ``nnum``. + """ + return self._get_nodes_result(rnum, "displacement", in_nodal_coord_sys, nodes) + + def nodal_solution( + self, + rnum: Rnum, + in_nodal_coord_sys: bool = False, + nodes: Nodes = None, + return_temperature: bool = False, + ) -> ReturnData: + """Returns the DOF solution for each node in the global + cartesian coordinate system or nodal coordinate system. + + Solution may be nodal temperatures or nodal displacements + depending on the type of the solution. + + Parameters + ---------- + rnum + Cumulative result number with zero based indexing, or a + list containing (step, substep) of the requested result. + + in_nodal_coord_sys + When ``True``, returns results in the nodal coordinate + system. Default ``False``. + + nodes + Select a limited subset of nodes. Can be a nodal + component or array of node numbers. For example + + * ``"MY_COMPONENT"`` + * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` + * ``np.arange(1000, 2001)`` + + return_temperature + When ``True``, returns the nodal temperature instead of + the displacement. Default ``False``. + + Returns + ------- + int np.ndarray + Node numbers associated with the results. + + float np.ndarray + Array of nodal displacements or nodal temperatures. Array + is (``nnod`` x ``sumdof``), the number of nodes by the + number of degrees of freedom which includes ``numdof`` and + ``nfldof`` + + Examples + -------- + Return the nodal solution (in this case, displacement) for the + first result of ``"file.rst"`` + + >>> from ansys.mapdl.core.reader import DPFResult as Result + >>> rst = Result('file.rst') + >>> nnum, data = rst.nodal_solution(0) + + Return the nodal solution just for the nodal component + ``'MY_COMPONENT'``. + + >>> nnum, data = rst.nodal_solution(0, nodes='MY_COMPONENT') + + Return the nodal solution just for the nodes from 20 through 50. + + >>> nnum, data = rst.nodal_solution(0, nodes=range(20, 51)) + + Notes + ----- + Some solution results may not include results for each node. + These results are removed by and the node numbers of the + solution results are reflected in ``nnum``. + """ + + if hasattr(self.model.results, "displacement") and not return_temperature: + return self.nodal_displacement(rnum, in_nodal_coord_sys, nodes) + elif hasattr(self.model.results, "temperature"): + return self.nodal_temperature(rnum, nodes) + else: + raise ResultNotFound( + "The current analysis does not have 'displacement' or 'temperature' results." + ) + + def nodal_temperature(self, rnum: Rnum, nodes: Nodes = None) -> ReturnData: + """Retrieves the temperature for each node in the + solution. + + The order of the results corresponds to the sorted node + numbering. + + Equivalent MAPDL command: PRNSOL, TEMP + + Parameters + ---------- + rnum : int or list + Cumulative result number with zero based indexing, or a + list containing (step, substep) of the requested result. + + nodes : str, sequence of int or str, optional + Select a limited subset of nodes. Can be a nodal + component or array of node numbers. For example + + * ``"MY_COMPONENT"`` + * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` + * ``np.arange(1000, 2001)`` + + Returns + ------- + nnum : numpy.ndarray + Node numbers of the result. + + temperature : numpy.ndarray + Temperature at each node. + + Examples + -------- + >>> from ansys.mapdl.core.reader import DPFResult as Result + >>> rst = Result('file.rst') + >>> nnum, temp = rst.nodal_temperature(0) + + Return the temperature just for the nodal component + ``'MY_COMPONENT'``. + + >>> nnum, temp = rst.nodal_stress(0, nodes='MY_COMPONENT') + + Return the temperature just for the nodes from 20 through 50. + + >>> nnum, temp = rst.nodal_solution(0, nodes=range(20, 51)) + """ + return self._get_nodes_result(rnum, "temperature", nodes=nodes) + + def nodal_voltage( + self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None + ) -> ReturnData: + """Retrieves the voltage for each node in the + solution. + + The order of the results corresponds to the sorted node + numbering. + + Equivalent MAPDL command: PRNSOL, VOLT + + Parameters + ---------- + rnum + Cumulative result number with zero based indexing, or a + list containing (step, substep) of the requested result. + + in_nodal_coord_sys + When ``True``, returns results in the nodal coordinate + system. Default ``False``. + + nodes + Select a limited subset of nodes. Can be a nodal + component or array of node numbers. For example + + * ``"MY_COMPONENT"`` + * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` + * ``np.arange(1000, 2001)`` + + Returns + ------- + numpy.ndarray + Node numbers of the result. + + numpy.ndarray + Voltage at each node. + + Examples + -------- + >>> from ansys.mapdl.core.reader import DPFResult as Result + >>> rst = Result('file.rst') + >>> nnum, temp = rst.nodal_voltage(0) + + Return the voltage just for the nodal component + ``'MY_COMPONENT'``. + + >>> nnum, temp = rst.nodal_stress(0, nodes='MY_COMPONENT') + """ + return self._get_nodes_result( + rnum, "electric_potential", in_nodal_coord_sys, nodes + ) + + def element_stress( + self, + rnum: Rnum, + principal: bool = False, + in_element_coord_sys: bool = False, + elements: Elements = None, + **kwargs: Kwargs, + ): + """Retrieves the element component stresses. + + Equivalent ANSYS command: PRESOL, S + + Parameters + ---------- + rnum : int or list + Cumulative result number with zero based indexing, or a + list containing (step, substep) of the requested result. + + principal : bool, optional + Returns principal stresses instead of component stresses. + Default False. + + in_element_coord_sys : bool, optional + When ``True``, returns results in the element coordinate + system. Default ``False``. + + in_element_coord_sys : bool, optional + Returns the results in the element coordinate system. + Default False and will return the results in the global + coordinate system. + + elements : str, sequence of int or str, optional + Select a limited subset of elements. Can be a element + component or array of element numbers. For example + + * ``"MY_COMPONENT"`` + * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` + * ``np.arange(1000, 2001)`` + + **kwargs : optional keyword arguments + Hidden options for distributed result files. + + Returns + ------- + enum : np.ndarray + ANSYS element numbers corresponding to each element. + + element_stress : list + Stresses at each element for each node for Sx Sy Sz Sxy + Syz Sxz or SIGMA1, SIGMA2, SIGMA3, SINT, SEQV when + principal is True. + + enode : list + Node numbers corresponding to each element's stress + results. One list entry for each element. + + Examples + -------- + Element component stress for the first result set. + + >>> rst.element_stress(0) + + Element principal stress for the first result set. + + >>> enum, element_stress, enode = result.element_stress(0, principal=True) + + Notes + ----- + Shell stresses for element 181 are returned for top and bottom + layers. Results are ordered such that the top layer and then + the bottom layer is reported. + """ + if kwargs: + raise NotImplementedError( + "Hidden options for distributed result files are not implemented." + ) + + if principal: + op = self._get_elem_result( + rnum, + "stress", + in_element_coord_sys=in_element_coord_sys, + elements=elements, + return_operator=True, + ) + return self._get_principal(op) + return self._get_elem_result(rnum, "stress", in_element_coord_sys, elements) + + def element_nodal_stress( + self, + rnum: Rnum, + principal: bool = False, + in_element_coord_sys: bool = False, + elements: Elements = None, + **kwargs: Kwargs, + ): + """Retrieves the nodal stresses for each element. + + Parameters + ---------- + rnum + Cumulative result number with zero based indexing, or a list containing + (step, substep) of the requested result. + + principal + Returns principal stresses instead of component stresses. + Default False. + + in_element_coord_sys + Returns the results in the element coordinate system if ``True``. + Else, it returns the results in the global coordinate system. + Default False + + elements + Select a limited subset of elements. Can be a element + component or array of element numbers. For example: + + * ``"MY_COMPONENT"`` + * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` + * ``np.arange(1000, 2001)`` + + **kwargs + Hidden options for distributed result files. + + Returns + ------- + np.ndarray + ANSYS element numbers corresponding to each element. + + list + Stresses at each element for each node for Sx Sy Sz Sxy + Syz Sxz or SIGMA1, SIGMA2, SIGMA3, SINT, SEQV when + principal is True. + + list + Node numbers corresponding to each element's stress + results. One list entry for each element. + + Examples + -------- + Element component stress for the first result set. + + >>> rst.element_stress(0) + + Element principal stress for the first result set. + + >>> enum, element_stress, enode = result.element_stress(0, principal=True) + + Notes + ----- + Shell stresses for element 181 are returned for top and bottom + layers. Results are ordered such that the top layer and then + the bottom layer is reported. + """ + if kwargs: + raise NotImplementedError( + "Hidden options for distributed result files are not implemented." + ) + + if principal: + op = self._get_elemnodal_result( + rnum, + "stress", + in_element_coord_sys=in_element_coord_sys, + elements=elements, + return_operator=True, + ) + return self._get_principal(op) + return self._get_elemnodal_result( + rnum, "stress", in_element_coord_sys, elements + ) + + def nodal_elastic_strain( + self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None + ) -> ReturnData: + """Nodal component elastic strains. This record contains + strains in the order ``X, Y, Z, XY, YZ, XZ, EQV``. + + Elastic strains can be can be nodal values extrapolated from + the integration points or values at the integration points + moved to the nodes. + + Equivalent MAPDL command: ``PRNSOL, EPEL`` + + Parameters + ---------- + rnum + Cumulative result number with zero based indexing, or a + list containing (step, substep) of the requested result. + + in_nodal_coord_sys + When ``True``, returns results in the nodal coordinate + system. Default False. + + nodes + Select a limited subset of nodes. Can be a nodal + component or array of node numbers. For example + + * ``"MY_COMPONENT"`` + * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` + * ``np.arange(1000, 2001)`` + + Returns + ------- + np.ndarray + MAPDL node numbers. + + np.ndarray + Nodal component elastic strains. Array is in the order + ``X, Y, Z, XY, YZ, XZ, EQV``. + + .. versionchanged:: 0.64 + The nodes with no values are now equals to zero. + The results of the midnodes are also calculated and + presented. + + Examples + -------- + Load the nodal elastic strain for the first result. + + >>> from ansys.mapdl.core.reader import DPFResult as Result + >>> rst = Result('file.rst') + >>> nnum, elastic_strain = rst.nodal_elastic_strain(0) + + Return the nodal elastic strain just for the nodal component + ``'MY_COMPONENT'``. + + >>> nnum, elastic_strain = rst.nodal_elastic_strain(0, nodes='MY_COMPONENT') + + Return the nodal elastic strain just for the nodes from 20 through 50. + + >>> nnum, elastic_strain = rst.nodal_elastic_strain(0, nodes=range(20, 51)) + + Notes + ----- + Nodes without a strain will be NAN. + + .. + """ + return self._get_nodes_result( + rnum, "elastic_strain", in_nodal_coord_sys=in_nodal_coord_sys, nodes=nodes + ) + + def nodal_plastic_strain( + self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None + ) -> ReturnData: + """Nodal component plastic strains. + + This record contains strains in the order: + ``X, Y, Z, XY, YZ, XZ, EQV``. + + Plastic strains are always values at the integration points + moved to the nodes. + + Parameters + ---------- + rnum + Cumulative result number with zero based indexing, or a + list containing (step, substep) of the requested result. + + in_nodal_coord_sys + When ``True``, returns results in the nodal coordinate + system. Default False. + + nodes + Select a limited subset of nodes. Can be a nodal + component or array of node numbers. For example + + * ``"MY_COMPONENT"`` + * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` + * ``np.arange(1000, 2001)`` + + Returns + ------- + np.ndarray + MAPDL node numbers. + + np.ndarray + Nodal component plastic strains. Array is in the order + ``X, Y, Z, XY, YZ, XZ, EQV``. + + Examples + -------- + Load the nodal plastic strain for the first solution. + + >>> from ansys.mapdl.core.reader import DPFResult as Result + >>> rst = Result('file.rst') + >>> nnum, plastic_strain = rst.nodal_plastic_strain(0) + + Return the nodal plastic strain just for the nodal component + ``'MY_COMPONENT'``. + + >>> nnum, plastic_strain = rst.nodal_plastic_strain(0, nodes='MY_COMPONENT') + + Return the nodal plastic strain just for the nodes from 20 + through 50. + + >>> nnum, plastic_strain = rst.nodal_plastic_strain(0, nodes=range(20, 51)) + """ + return self._get_nodes_result(rnum, "plastic_strain", in_nodal_coord_sys, nodes) + + def nodal_acceleration( + self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None + ) -> ReturnData: + """Nodal velocities for a given result set. + + Parameters + ---------- + rnum + Cumulative result number with zero based indexing, or a + list containing (step, substep) of the requested result. + + in_nodal_coord_sys + When ``True``, returns results in the nodal coordinate + system. Default False. + + nodes + Select a limited subset of nodes. Can be a nodal + component or array of node numbers. For example + + * ``"MY_COMPONENT"`` + * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` + * ``np.arange(1000, 2001)`` + + Returns + ------- + np.ndarray + Node numbers associated with the results. + + np.ndarray + Array of nodal accelerations. Array is (``nnod`` x + ``sumdof``), the number of nodes by the number of degrees + of freedom which includes ``numdof`` and ``nfldof`` + + Examples + -------- + >>> from ansys.mapdl.core.reader import DPFResult as Result + >>> rst = Result('file.rst') + >>> nnum, data = rst.nodal_acceleration(0) + + Notes + ----- + Some solution results may not include results for each node. + These results are removed by and the node numbers of the + solution results are reflected in ``nnum``. + """ + return self._get_nodes_result(rnum, "acceleration", in_nodal_coord_sys, nodes) + + def nodal_reaction_forces( + self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None + ) -> ReturnData: + """Nodal reaction forces. + + Parameters + ---------- + rnum + Cumulative result number with zero based indexing, or a + list containing (step, substep) of the requested result. + + in_nodal_coord_sys + When ``True``, returns results in the nodal coordinate + system. Default False. + + nodes + Select a limited subset of nodes. Can be a nodal + component or array of node numbers. For example + + * ``"MY_COMPONENT"`` + * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` + * ``np.arange(1000, 2001)`` + + Returns + ------- + np.ndarray + Node numbers corresponding to the reaction forces. Node + numbers may be repeated if there is more than one degree + of freedom for each node. + + np.ndarray + Degree of freedom corresponding to each node using the + MAPDL degree of freedom reference table. + + Examples + -------- + Get the nodal reaction forces for the first result and print + the reaction forces of a single node. + + >>> from ansys.mapdl.core.reader import DPFResult as Result + >>> rst = Result('file.rst') + >>> rforces, nnum, dof = rst.nodal_reaction_forces(0) + >>> dof_ref = rst.result_dof(0) + >>> rforces[:3], nnum[:3], dof[:3], dof_ref + (array([ 24102.21376091, -109357.01854005, 22899.5303263 ]), + array([4142, 4142, 4142]), + array([1, 2, 3], dtype=int32), + ['UX', 'UY', 'UZ']) + """ + return self._get_nodes_result(rnum, "reaction_force", in_nodal_coord_sys, nodes) + + def nodal_stress( + self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None + ) -> ReturnData: + """Retrieves the component stresses for each node in the + solution. + + The order of the results corresponds to the sorted node + numbering. + + Computes the nodal stress by averaging the stress for each + element at each node. Due to the discontinuities across + elements, stresses will vary based on the element they are + evaluated from. + + Parameters + ---------- + rnum + Cumulative result number with zero based indexing, or a + list containing (step, substep) of the requested result. + + in_nodal_coord_sys + When ``True``, returns results in the nodal coordinate + system. Default False. + + nodes + Select a limited subset of nodes. Can be a nodal + component or array of node numbers. For example + + * ``"MY_COMPONENT"`` + * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` + * ``np.arange(1000, 2001)`` + + Returns + ------- + nnum : numpy.ndarray + Node numbers of the result. + + stress : numpy.ndarray + Stresses at ``X, Y, Z, XY, YZ, XZ`` averaged at each corner + node. + + Examples + -------- + >>> from ansys.mapdl.core.reader import DPFResult as Result + >>> rst = Result('file.rst') + >>> nnum, stress = rst.nodal_stress(0) + + Return the nodal stress just for the nodal component + ``'MY_COMPONENT'``. + + >>> nnum, stress = rst.nodal_stress(0, nodes='MY_COMPONENT') + + Return the nodal stress just for the nodes from 20 through 50. + + >>> nnum, stress = rst.nodal_solution(0, nodes=range(20, 51)) + + Notes + ----- + Nodes without a stress value will be NAN. + Equivalent ANSYS command: PRNSOL, S + """ + return self._get_nodes_result(rnum, "stress", in_nodal_coord_sys, nodes) + + def nodal_thermal_strain( + self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None + ) -> ReturnData: + """Nodal component thermal strain. + + This record contains strains in the order X, Y, Z, XY, YZ, XZ, + EQV, and eswell (element swelling strain). Thermal strains + are always values at the integration points moved to the + nodes. + + Equivalent MAPDL command: PRNSOL, EPTH, COMP + + Parameters + ---------- + rnum + Cumulative result number with zero based indexing, or a + list containing (step, substep) of the requested result. + + in_nodal_coord_sys + When ``True``, returns results in the nodal coordinate + system. Default False. + + nodes + Select a limited subset of nodes. Can be a nodal + component or array of node numbers. For example + + * ``"MY_COMPONENT"`` + * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` + * ``np.arange(1000, 2001)`` + + Returns + ------- + np.ndarray + MAPDL node numbers. + + np.ndarray + Nodal component plastic strains. Array is in the order + ``X, Y, Z, XY, YZ, XZ, EQV, ESWELL`` + + Examples + -------- + Load the nodal thermal strain for the first solution. + + >>> from ansys.mapdl.core.reader import DPFResult as Result + >>> rst = Result('file.rst') + >>> nnum, thermal_strain = rst.nodal_thermal_strain(0) + + Return the nodal thermal strain just for the nodal component + ``'MY_COMPONENT'``. + + >>> nnum, thermal_strain = rst.nodal_thermal_strain(0, nodes='MY_COMPONENT') + + Return the nodal thermal strain just for the nodes from 20 through 50. + + >>> nnum, thermal_strain = rst.nodal_thermal_strain(0, nodes=range(20, 51)) + """ + return self._get_nodes_result(rnum, "thermal_strain", in_nodal_coord_sys, nodes) + + def nodal_velocity( + self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None + ) -> ReturnData: + """Nodal velocities for a given result set. + + Parameters + ---------- + rnum + Cumulative result number with zero based indexing, or a + list containing (step, substep) of the requested result. + + in_nodal_coord_sys + When ``True``, returns results in the nodal coordinate + system. Default False. + + nodes + Select a limited subset of nodes. Can be a nodal + component or array of node numbers. For example + + * ``"MY_COMPONENT"`` + * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` + * ``np.arange(1000, 2001)`` + + Returns + ------- + nnum : int np.ndarray + Node numbers associated with the results. + + result : float np.ndarray + Array of nodal velocities. Array is (``nnod`` x + ``sumdof``), the number of nodes by the number of degrees + of freedom which includes ``numdof`` and ``nfldof`` + + Examples + -------- + >>> from ansys.mapdl.core.reader import DPFResult as Result + >>> rst = Result('file.rst') + >>> nnum, data = rst.nodal_velocity(0) + + Notes + ----- + Some solution results may not include results for each node. + These results are removed by and the node numbers of the + solution results are reflected in ``nnum``. + """ + return self._get_nodes_result(rnum, "velocity", in_nodal_coord_sys, nodes) + + def nodal_static_forces( + self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None + ) -> ReturnData: + """Return the nodal forces averaged at the nodes. + + Nodal forces are computed on an element by element basis, and + this method averages the nodal forces for each element for + each node. + + Parameters + ---------- + rnum + Cumulative result number with zero based indexing, or a + list containing (step, substep) of the requested result. + + in_nodal_coord_sys + When ``True``, returns results in the nodal coordinate + system. Default False. + + nodes + Select a limited subset of nodes. Can be a nodal + component or array of node numbers. For example + + * ``"MY_COMPONENT"`` + * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` + * ``np.arange(1000, 2001)`` + + Returns + ------- + np.ndarray + MAPDL node numbers. + + np.ndarray + Averaged nodal forces. Array is sized ``[nnod x numdof]`` + where ``nnod`` is the number of nodes and ``numdof`` is the + number of degrees of freedom for this solution. + + Examples + -------- + Load the nodal static forces for the first result using the + example hexahedral result file. + + >>> from ansys.mapdl import reader as pymapdl_reader + >>> from ansys.mapdl.reader import examples + >>> rst = pymapdl_reader.read_binary(examples.rstfile) + >>> nnum, forces = rst.nodal_static_forces(0) + + Return the nodal static forces just for the nodal component + ``'MY_COMPONENT'``. + + >>> nnum, forces = rst.nodal_static_forces(0, nodes='MY_COMPONENT') + + Return the nodal static forces just for the nodes from 20 through 50. + + >>> nnum, forces = rst.nodal_static_forces(0, nodes=range(20, 51)) + + Notes + ----- + Nodes without a a nodal will be NAN. These are generally + midside (quadratic) nodes. + """ + return self._get_nodes_result(rnum, "nodal_force", in_nodal_coord_sys, nodes) + + def principal_nodal_stress( + self, rnum: Rnum, in_nodal_coord_sys: bool = False, nodes: Nodes = None + ) -> ReturnData: + """Computes the principal component stresses for each node in + the solution. + + Parameters + ---------- + rnum + Cumulative result number with zero based indexing, or a + list containing (step, substep) of the requested result. + + in_nodal_coord_sys + If True, return the results in the nodal coordinate system. + + nodes + Select a limited subset of nodes. Can be a nodal + component or array of node numbers. For example + + * ``"MY_COMPONENT"`` + * ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` + * ``np.arange(1000, 2001)`` + + Returns + ------- + numpy.ndarray + Node numbers of the result. + + numpy.ndarray + Principal stresses, stress intensity, and equivalent stress. + [sigma1, sigma2, sigma3, sint, seqv] + + Examples + -------- + Load the principal nodal stress for the first solution. + + >>> from ansys.mapdl.core.reader import DPFResult as Result + >>> rst = Result('file.rst') + >>> nnum, stress = rst.principal_nodal_stress(0) + + Notes + ----- + ANSYS equivalent of: + PRNSOL, S, PRIN + + which returns: + S1, S2, S3 principal stresses, SINT stress intensity, and SEQV + equivalent stress. + + Internal averaging algorithm averages the component values + from the elements at a common node and then calculates the + principal using the averaged value. + + See the MAPDL ``AVPRIN`` command for more details. + ``ansys-mapdl-reader`` uses the default ``AVPRIN, 0`` option. + """ + op = self._get_nodes_result( + rnum, + "stress", + in_nodal_coord_sys=in_nodal_coord_sys, + nodes=nodes, + return_operator=True, + ) + return self._get_principal(op) diff --git a/src/ansys/mapdl/core/reader/plotting.py b/src/ansys/mapdl/core/reader/plotting.py new file mode 100644 index 00000000000..7b004a6e824 --- /dev/null +++ b/src/ansys/mapdl/core/reader/plotting.py @@ -0,0 +1,119 @@ +# Copyright (C) 2016 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Class to provide with plotting capabilities for DPF results""" + +from ansys.mapdl.core.reader.core import DPFResultCore +from ansys.mapdl.core.reader.types import ( + ComponentsDirections, + Kwargs, + MAPDLComponents, + Rnum, +) + + +class DPFResultPlotting(DPFResultCore): + """Provides plotting capabilities for DPF results.""" + + def plot_nodal_stress( + self, + rnum: Rnum, + comp: ComponentsDirections | None = None, + show_displacement: bool = False, + displacement_factor: int = 1, + node_components: MAPDLComponents | None = None, + element_components: MAPDLComponents | None = None, + sel_type_all: bool = True, + treat_nan_as_zero: bool = True, + **kwargs: Kwargs, + ): + """Plots the stresses at each node in the solution. + + Parameters + ---------- + rnum : int or list + Cumulative result number with zero based indexing, or a + list containing (step, substep) of the requested result. + + comp : str, optional + Stress component to display. Available options: + - ``"X"`` + - ``"Y"`` + - ``"Z"`` + - ``"XY"`` + - ``"YZ"`` + - ``"XZ"`` + + show_displacement + If True, displays the displacement along with the stress + plot. Default is False. + + displacement_factor + Factor by which to scale the displacement plot. Default is 1. + + node_components : list, optional + Accepts either a string or a list strings of node + components to plot. For example: + ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` + + element_components : list, optional + Accepts either a string or a list strings of element + components to plot. For example: + ``['MY_COMPONENT', 'MY_OTHER_COMPONENT]`` + + sel_type_all : bool, optional + If node_components is specified, plots those elements + containing all nodes of the component. Default ``True``. + + treat_nan_as_zero : bool, optional + Treat NAN values (i.e. stresses at midside nodes) as zero + when plotting. + + **kwargs + Additional keyword arguments. See ``help(pyvista.plot)`` + + Returns + ------- + cpos : list + 3 x 3 vtk camera position. + + Examples + -------- + Plot the X component nodal stress while showing displacement. + + >>> rst.plot_nodal_stress(0, comp='x', show_displacement=True) + """ + # if not comp: + # comp = "X" + + # ind = COMPONENTS.index(comp) + + # op = self._get_nodes_result( + # rnum, + # "stress", + # nodes=node_components, + # in_nodal_coord_sys=False, + # return_operator=True, + # ) + # fc = op.outputs.fields_as_fields_container()[0] + + raise NotImplementedError("WIP") From b7dd0b4ee34b5aa0e5047e34e6e5af81ccef575e Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Tue, 5 Aug 2025 10:55:58 +0200 Subject: [PATCH 4/9] refactor: add return type hints and warnings for methods not ported to DPF-based Results backend --- src/ansys/mapdl/core/reader/data.py | 51 ++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/src/ansys/mapdl/core/reader/data.py b/src/ansys/mapdl/core/reader/data.py index 7792b439ced..839121b559b 100644 --- a/src/ansys/mapdl/core/reader/data.py +++ b/src/ansys/mapdl/core/reader/data.py @@ -42,9 +42,13 @@ class DPFResultData(DPFResultCore): """Provides methods for accessing and manipulating DPF result data.""" - def nodal_input_force(self, rnum: Rnum): + def nodal_input_force(self, rnum: Rnum) -> None: """Nodal input force for a given result number. + .. warning:: This method has not been ported to the new DPF-based Results backend. + If you still want to use it, you can switch to 'pymapdl-reader' backend by setting + `mapdl.use_reader_backend=True`. + Nodal input force is generally set with the APDL command ``F``. For example, ``F, 25, FX, 0.001`` @@ -86,9 +90,13 @@ def nodal_input_force(self, rnum: Rnum): def element_solution_data( self, rnum: Rnum, datatype: str, sort: bool = True, **kwargs: Kwargs - ): + ) -> None: """Retrieves element solution data. Similar to ETABLE. + .. warning:: This method has not been ported to the new DPF-based Results backend. + If you still want to use it, you can switch to 'pymapdl-reader' backend by setting + `mapdl.use_reader_backend=True`. + Parameters ---------- rnum : int or list @@ -187,9 +195,13 @@ def element_solution_data( NOT_AVAILABLE_METHOD.format(method="element_solution_data") ) - def result_dof(self, rnum: Rnum): + def result_dof(self, rnum: Rnum) -> None: """Return a list of degrees of freedom for a given result number. + .. warning:: This method has not been ported to the new DPF-based Results backend. + If you still want to use it, you can switch to 'pymapdl-reader' backend by setting + `mapdl.use_reader_backend=True`. + Parameters ---------- rnum : int or list @@ -209,9 +221,13 @@ def result_dof(self, rnum: Rnum): # To be done later raise NotImplementedError(NOT_AVAILABLE_METHOD.format(method="result_dof")) - def cs_4x4(self, cs_cord: int, as_vtk_matrix: bool = False): + def cs_4x4(self, cs_cord: int, as_vtk_matrix: bool = False) -> None: """Return a 4x4 transformation matrix for a given coordinate system. + .. warning:: This method has not been ported to the new DPF-based Results backend. + If you still want to use it, you can switch to 'pymapdl-reader' backend by setting + `mapdl.use_reader_backend=True`. + Parameters ---------- cs_cord : int @@ -253,11 +269,12 @@ def cs_4x4(self, cs_cord: int, as_vtk_matrix: bool = False): """ raise NotImplementedError(NOT_AVAILABLE_METHOD.format(method="cs_4x4")) - def read_record(self, pointer: int, return_bufsize: bool = False): + def read_record(self, pointer: int, return_bufsize: bool = False) -> None: """Reads a record at a given position. - Because ANSYS 19.0+ uses compression by default, you must use - this method rather than ``np.fromfile``. + .. warning:: This method has not been ported to the new DPF-based Results backend. + If you still want to use it, you can switch to 'pymapdl-reader' backend by setting + `mapdl.use_reader_backend=True`. Parameters ---------- @@ -281,9 +298,13 @@ def read_record(self, pointer: int, return_bufsize: bool = False): """ raise NotImplementedError(NOT_AVAILABLE_METHOD.format(method="read_record")) - def text_result_table(self, rnum: Rnum): + def text_result_table(self, rnum: Rnum) -> None: """Returns a text result table for plotting. + .. warning:: This method has not been ported to the new DPF-based Results backend. + If you still want to use it, you can switch to 'pymapdl-reader' backend by setting + `mapdl.use_reader_backend=True`. + Parameters ---------- rnum @@ -293,8 +314,12 @@ def text_result_table(self, rnum: Rnum): NOT_AVAILABLE_METHOD.format(method="text_result_table") ) - def write_tables(self, filename: str | Path): - """Write binary tables to ASCII. Assumes int32 + def write_tables(self, filename: str | Path) -> None: + """Write binary tables to ASCII. Assumes int32. + + .. warning:: This method has not been ported to the new DPF-based Results backend. + If you still want to use it, you can switch to 'pymapdl-reader' backend by setting + `mapdl.use_reader_backend=True`. Parameters ---------- @@ -309,9 +334,13 @@ def write_tables(self, filename: str | Path): def overwrite_element_solution_records( self, element_data: dict[int, np.ndarray], rnum: Rnum, solution_type: str - ): + ) -> None: """Overwrite element solution record. + .. warning:: This method has not been ported to the new DPF-based Results backend. + If you still want to use it, you can switch to 'pymapdl-reader' backend by setting + `mapdl.use_reader_backend=True`. + This method replaces solution data for a set of elements at a result index for a given solution type. The number of items in ``data`` must match the number of items in the record. From eb5e09a7c7b0ad68c030f8e04b8abdea0f973918 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Tue, 5 Aug 2025 08:58:39 +0000 Subject: [PATCH 5/9] chore: adding changelog file 4144.miscellaneous.md [dependabot-skip] --- doc/changelog.d/4144.miscellaneous.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/4144.miscellaneous.md diff --git a/doc/changelog.d/4144.miscellaneous.md b/doc/changelog.d/4144.miscellaneous.md new file mode 100644 index 00000000000..8f109427859 --- /dev/null +++ b/doc/changelog.d/4144.miscellaneous.md @@ -0,0 +1 @@ +Refactor: splitting result file \ No newline at end of file From 316b87258f06702d31d2a881af83a08cc31b7a90 Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Tue, 5 Aug 2025 11:03:29 +0200 Subject: [PATCH 6/9] refactor: update warning messages for methods not ported to DPF-based Results backend --- src/ansys/mapdl/core/reader/core.py | 23 ++++++++++++++++++++--- src/ansys/mapdl/core/reader/data.py | 26 +++++++++++++++++--------- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/ansys/mapdl/core/reader/core.py b/src/ansys/mapdl/core/reader/core.py index 399d7ed0152..05cc9b8b698 100644 --- a/src/ansys/mapdl/core/reader/core.py +++ b/src/ansys/mapdl/core/reader/core.py @@ -1592,12 +1592,17 @@ def element_lookup(self, element_id: int) -> int: f"Available element IDs: {list(mapping.keys())}" ) - return mapping[element_id] + return int(mapping[element_id]) - def solution_info(self, rnum: Rnum): + def solution_info(self, rnum: Rnum) -> dict[str, Any]: """Return an informative dictionary of solution data for a result. + .. warning:: This method has not been ported to the new DPF-based Results backend + and it is kept here for future references. + If you still want to use it, you can switch to 'pymapdl-reader' backend by setting + `mapdl.use_reader_backend=True`. + Parameters ---------- rnum : int or list @@ -1679,7 +1684,19 @@ def solution_info(self, rnum: Rnum): raise NotImplementedError(NOT_AVAILABLE_METHOD.format(method="solution_info")) @property - def subtitle(self): + def subtitle(self) -> str: + """Subtitle of the model in the database. + + .. warning:: This method has not been ported to the new DPF-based Results backend + and it is kept here for future references. + If you still want to use it, you can switch to 'pymapdl-reader' backend by setting + `mapdl.use_reader_backend=True`. + + Returns + ------- + str + Subtitle of the model. + """ raise NotImplementedError(NOT_AVAILABLE_METHOD.format(method="subtitle")) def _get_comp_dict(self, entity: str) -> dict[str, tuple[int]]: diff --git a/src/ansys/mapdl/core/reader/data.py b/src/ansys/mapdl/core/reader/data.py index 839121b559b..0f915999fbd 100644 --- a/src/ansys/mapdl/core/reader/data.py +++ b/src/ansys/mapdl/core/reader/data.py @@ -45,7 +45,8 @@ class DPFResultData(DPFResultCore): def nodal_input_force(self, rnum: Rnum) -> None: """Nodal input force for a given result number. - .. warning:: This method has not been ported to the new DPF-based Results backend. + .. warning:: This method has not been ported to the new DPF-based Results backend + and it is kept here for future references. If you still want to use it, you can switch to 'pymapdl-reader' backend by setting `mapdl.use_reader_backend=True`. @@ -93,7 +94,8 @@ def element_solution_data( ) -> None: """Retrieves element solution data. Similar to ETABLE. - .. warning:: This method has not been ported to the new DPF-based Results backend. + .. warning:: This method has not been ported to the new DPF-based Results backend + and it is kept here for future references. If you still want to use it, you can switch to 'pymapdl-reader' backend by setting `mapdl.use_reader_backend=True`. @@ -198,7 +200,8 @@ def element_solution_data( def result_dof(self, rnum: Rnum) -> None: """Return a list of degrees of freedom for a given result number. - .. warning:: This method has not been ported to the new DPF-based Results backend. + .. warning:: This method has not been ported to the new DPF-based Results backend + and it is kept here for future references. If you still want to use it, you can switch to 'pymapdl-reader' backend by setting `mapdl.use_reader_backend=True`. @@ -224,7 +227,8 @@ def result_dof(self, rnum: Rnum) -> None: def cs_4x4(self, cs_cord: int, as_vtk_matrix: bool = False) -> None: """Return a 4x4 transformation matrix for a given coordinate system. - .. warning:: This method has not been ported to the new DPF-based Results backend. + .. warning:: This method has not been ported to the new DPF-based Results backend + and it is kept here for future references. If you still want to use it, you can switch to 'pymapdl-reader' backend by setting `mapdl.use_reader_backend=True`. @@ -272,7 +276,8 @@ def cs_4x4(self, cs_cord: int, as_vtk_matrix: bool = False) -> None: def read_record(self, pointer: int, return_bufsize: bool = False) -> None: """Reads a record at a given position. - .. warning:: This method has not been ported to the new DPF-based Results backend. + .. warning:: This method has not been ported to the new DPF-based Results backend + and it is kept here for future references. If you still want to use it, you can switch to 'pymapdl-reader' backend by setting `mapdl.use_reader_backend=True`. @@ -301,7 +306,8 @@ def read_record(self, pointer: int, return_bufsize: bool = False) -> None: def text_result_table(self, rnum: Rnum) -> None: """Returns a text result table for plotting. - .. warning:: This method has not been ported to the new DPF-based Results backend. + .. warning:: This method has not been ported to the new DPF-based Results backend + and it is kept here for future references. If you still want to use it, you can switch to 'pymapdl-reader' backend by setting `mapdl.use_reader_backend=True`. @@ -317,7 +323,8 @@ def text_result_table(self, rnum: Rnum) -> None: def write_tables(self, filename: str | Path) -> None: """Write binary tables to ASCII. Assumes int32. - .. warning:: This method has not been ported to the new DPF-based Results backend. + .. warning:: This method has not been ported to the new DPF-based Results backend + and it is kept here for future references. If you still want to use it, you can switch to 'pymapdl-reader' backend by setting `mapdl.use_reader_backend=True`. @@ -337,7 +344,8 @@ def overwrite_element_solution_records( ) -> None: """Overwrite element solution record. - .. warning:: This method has not been ported to the new DPF-based Results backend. + .. warning:: This method has not been ported to the new DPF-based Results backend + and it is kept here for future references. If you still want to use it, you can switch to 'pymapdl-reader' backend by setting `mapdl.use_reader_backend=True`. @@ -395,7 +403,7 @@ def overwrite_element_solution_records( Examples -------- - Overwrite the elastic strain record for elements 1 and 2 with + Overwrite the elastic strain record for elements 1 and 2 for the first result with random data. >>> from ansys.mapdl import reader as pymapdl_reader From 9556a4af219a8eeb34c8bfc7b2fcc41c5ad25c5f Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Tue, 5 Aug 2025 12:06:58 +0200 Subject: [PATCH 7/9] refactor: update import paths for COMPONENTS and DPFResult in test_result.py --- tests/test_result.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_result.py b/tests/test_result.py index 69209b983f9..995f8881f84 100644 --- a/tests/test_result.py +++ b/tests/test_result.py @@ -66,7 +66,7 @@ else: from ansys.dpf import core as dpf_core from ansys.dpf.gate.errors import DPFServerException - from ansys.mapdl.core.reader.core import COMPONENTS + from ansys.mapdl.core.reader.constants import COMPONENTS from ansys.mapdl.reader import read_binary from ansys.mapdl.reader.rst import Result @@ -490,9 +490,7 @@ def test_not_implemented(self, result, method): (True, Result), ( False, - __import__( - "ansys.mapdl.core.reader.result", fromlist=["DPFResult"] - ).DPFResult, + __import__("ansys.mapdl.core.reader", fromlist=["DPFResult"]).DPFResult, ), ], ) From 82d72757f35177e231ee5ecf33c7a63597688ba0 Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Tue, 5 Aug 2025 12:26:39 +0200 Subject: [PATCH 8/9] refactor: enhance error handling for unimplemented methods in DPF backend --- src/ansys/mapdl/core/reader/constants.py | 7 ++++-- src/ansys/mapdl/core/reader/core.py | 29 ++++++++++++++++++++-- src/ansys/mapdl/core/reader/data.py | 31 ++++++++++-------------- 3 files changed, 45 insertions(+), 22 deletions(-) diff --git a/src/ansys/mapdl/core/reader/constants.py b/src/ansys/mapdl/core/reader/constants.py index 9bb5d6e74c6..0722f7cfe90 100644 --- a/src/ansys/mapdl/core/reader/constants.py +++ b/src/ansys/mapdl/core/reader/constants.py @@ -92,5 +92,8 @@ ] -NOT_AVAILABLE_METHOD = """The method '{method}' has not been ported to the new DPF-based Results backend. -If you still want to use it, you can switch to 'pymapdl-reader' backend.""" +NOT_AVAILABLE_METHOD: str = """The method '{method}' has not been ported to the new DPF-based Results backend. +If you still want to use it, you can switch to 'pymapdl-reader' backend using `mapdl.use_reader_backend = True`.""" + +NOT_AVAILABLE_ARGUMENT: str = """The argument '{argument}' in this function has not been ported to the new DPF-based Results backend. +If you still want to use it, you can switch to 'pymapdl-reader' backend using `mapdl.use_reader_backend = True`.""" diff --git a/src/ansys/mapdl/core/reader/core.py b/src/ansys/mapdl/core/reader/core.py index 05cc9b8b698..3764e01a6d8 100644 --- a/src/ansys/mapdl/core/reader/core.py +++ b/src/ansys/mapdl/core/reader/core.py @@ -52,6 +52,7 @@ from ansys.mapdl.core.reader.constants import ( LOCATION_MAPPING, MATERIAL_PROPERTIES, + NOT_AVAILABLE_ARGUMENT, NOT_AVAILABLE_METHOD, ) from ansys.mapdl.core.reader.types import ( @@ -88,6 +89,30 @@ def __init__(self, msg: str = ""): MapdlRuntimeError.__init__(self, msg) +class NotImplementedInDPFBackend(MapdlRuntimeError, NotImplementedError): + """Exception raised when a method is not implemented in the DPF backend. + + Parameters + ---------- + method : str + Name of the method that is not implemented. + """ + + def __init__(self, method: str = "", argument: str = ""): + """Initialize NotImplementedInDPFBackend exception. + + Parameters + ---------- + method : str + Name of the method that is not implemented. + """ + if argument: + msg = NOT_AVAILABLE_ARGUMENT.format(argument=argument) + else: + msg = NOT_AVAILABLE_METHOD.format(method=method) + MapdlRuntimeError.__init__(self, msg) + + def update_result(function: Callable[..., Any]) -> Callable[..., Any]: """ Decorator to wrap :class:`DPFResult ` @@ -1681,7 +1706,7 @@ def solution_info(self, rnum: Rnum) -> dict[str, Any]: if pmeth=1: p-method convergence values - pCnvVal : P-method convergence values """ - raise NotImplementedError(NOT_AVAILABLE_METHOD.format(method="solution_info")) + raise NotImplementedInDPFBackend(method="solution_info") @property def subtitle(self) -> str: @@ -1697,7 +1722,7 @@ def subtitle(self) -> str: str Subtitle of the model. """ - raise NotImplementedError(NOT_AVAILABLE_METHOD.format(method="subtitle")) + raise NotImplementedInDPFBackend(method="subtitle") def _get_comp_dict(self, entity: str) -> dict[str, tuple[int]]: """Get a dictionary of components given an entity diff --git a/src/ansys/mapdl/core/reader/data.py b/src/ansys/mapdl/core/reader/data.py index 0f915999fbd..c148be27290 100644 --- a/src/ansys/mapdl/core/reader/data.py +++ b/src/ansys/mapdl/core/reader/data.py @@ -27,8 +27,11 @@ import numpy as np -from ansys.mapdl.core.reader.constants import NOT_AVAILABLE_METHOD -from ansys.mapdl.core.reader.core import DPFResultCore, ResultNotFound +from ansys.mapdl.core.reader.core import ( + DPFResultCore, + NotImplementedInDPFBackend, + ResultNotFound, +) from ansys.mapdl.core.reader.types import ( Elements, Kwargs, @@ -85,9 +88,7 @@ def nodal_input_force(self, rnum: Rnum) -> None: array([30., 20., 40.])) """ # To be done later - raise NotImplementedError( - NOT_AVAILABLE_METHOD.format(method="nodal_input_force") - ) + raise NotImplementedInDPFBackend(method="nodal_input_force") def element_solution_data( self, rnum: Rnum, datatype: str, sort: bool = True, **kwargs: Kwargs @@ -193,9 +194,7 @@ def element_solution_data( >>> ansys.read_float_parameter('SD_LOC1(1)') -4266.19 """ - raise NotImplementedError( - NOT_AVAILABLE_METHOD.format(method="element_solution_data") - ) + raise NotImplementedInDPFBackend(method="element_solution_data") def result_dof(self, rnum: Rnum) -> None: """Return a list of degrees of freedom for a given result number. @@ -222,7 +221,7 @@ def result_dof(self, rnum: Rnum) -> None: ['UX', 'UY', 'UZ'] """ # To be done later - raise NotImplementedError(NOT_AVAILABLE_METHOD.format(method="result_dof")) + raise NotImplementedInDPFBackend(method="result_dof") def cs_4x4(self, cs_cord: int, as_vtk_matrix: bool = False) -> None: """Return a 4x4 transformation matrix for a given coordinate system. @@ -271,7 +270,7 @@ def cs_4x4(self, cs_cord: int, as_vtk_matrix: bool = False) -> None: [ 0., 1., 0., 0.], [ 0., 0., 0., 1.]]) """ - raise NotImplementedError(NOT_AVAILABLE_METHOD.format(method="cs_4x4")) + raise NotImplementedInDPFBackend(method="cs_4x4") def read_record(self, pointer: int, return_bufsize: bool = False) -> None: """Reads a record at a given position. @@ -301,7 +300,7 @@ def read_record(self, pointer: int, return_bufsize: bool = False) -> None: When ``return_bufsize`` is enabled, returns the number of words read. """ - raise NotImplementedError(NOT_AVAILABLE_METHOD.format(method="read_record")) + raise NotImplementedInDPFBackend(method="read_record") def text_result_table(self, rnum: Rnum) -> None: """Returns a text result table for plotting. @@ -316,9 +315,7 @@ def text_result_table(self, rnum: Rnum) -> None: rnum The result number to retrieve the table for. """ - raise NotImplementedError( - NOT_AVAILABLE_METHOD.format(method="text_result_table") - ) + raise NotImplementedInDPFBackend(method="text_result_table") def write_tables(self, filename: str | Path) -> None: """Write binary tables to ASCII. Assumes int32. @@ -337,7 +334,7 @@ def write_tables(self, filename: str | Path) -> None: -------- >>> rst.write_tables('tables.txt') """ - raise NotImplementedError(NOT_AVAILABLE_METHOD.format(method="write_tables")) + raise NotImplementedInDPFBackend(method="write_tables") def overwrite_element_solution_records( self, element_data: dict[int, np.ndarray], rnum: Rnum, solution_type: str @@ -412,9 +409,7 @@ def overwrite_element_solution_records( 2: np.random.random(56)} >>> rst.overwrite_element_solution_data(data, 0, 'EEL') """ - raise NotImplementedError( - NOT_AVAILABLE_METHOD.format(method="overwrite_element_solution_records") - ) + raise NotImplementedInDPFBackend(method="overwrite_element_solution_records") def nodal_time_history( self, solution_type: SolutionType = "NSL", in_nodal_coord_sys: bool = False From cfece8588cb28fa3d7a07f07d0eeccdc984ab514 Mon Sep 17 00:00:00 2001 From: German <28149841+germa89@users.noreply.github.com> Date: Tue, 5 Aug 2025 13:35:37 +0200 Subject: [PATCH 9/9] refactor: add missing overwrite_element_solution_record method --- src/ansys/mapdl/core/reader/data.py | 66 +++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/ansys/mapdl/core/reader/data.py b/src/ansys/mapdl/core/reader/data.py index c148be27290..ee39e49be88 100644 --- a/src/ansys/mapdl/core/reader/data.py +++ b/src/ansys/mapdl/core/reader/data.py @@ -336,6 +336,72 @@ def write_tables(self, filename: str | Path) -> None: """ raise NotImplementedInDPFBackend(method="write_tables") + def overwrite_element_solution_record( + self, + data: list[float] | np.ndarray, + rnum: Rnum, + solution_type: str, + element_id: int, + ): + """Overwrite element solution record. + This method replaces solution data for of an element at a + result index for a given solution type. The number of items + in ``data`` must match the number of items in the record. + If you are not sure how many records are in a given record, + use ``element_solution_data`` to retrieve all the records for + a given ``solution_type`` and check the number of items in the + record. + Note: The record being replaced cannot be a compressed record. + If the result file uses compression (default sparse + compression as of 2019R1), you can disable this within MAPDL + with: + ``/FCOMP, RST, 0`` + Parameters + ---------- + data : list or np.ndarray + Data that will replace the existing records. + rnum : int + Zero based result number. + solution_type : str + Element data type to overwrite. + - EMS: misc. data + - ENF: nodal forces + - ENS: nodal stresses + - ENG: volume and energies + - EGR: nodal gradients + - EEL: elastic strains + - EPL: plastic strains + - ECR: creep strains + - ETH: thermal strains + - EUL: euler angles + - EFX: nodal fluxes + - ELF: local forces + - EMN: misc. non-sum values + - ECD: element current densities + - ENL: nodal nonlinear data + - EHC: calculated heat generations + - EPT: element temperatures + - ESF: element surface stresses + - EDI: diffusion strains + - ETB: ETABLE items + - ECT: contact data + - EXY: integration point locations + - EBA: back stresses + - ESV: state variables + - MNL: material nonlinear record + element_id : int + Ansys element number (e.g. ``1``) + Examples + -------- + Overwrite the elastic strain record for element 1 for the + first result with random data. + >>> from ansys.mapdl import reader as pymapdl_reader + >>> rst = pymapdl_reader.read_binary('file.rst') + >>> data = np.random.random(56) + >>> rst.overwrite_element_solution_data(data, 0, 'EEL', 1) + """ + raise NotImplementedInDPFBackend(method="overwrite_element_solution_record") + def overwrite_element_solution_records( self, element_data: dict[int, np.ndarray], rnum: Rnum, solution_type: str ) -> None: