diff --git a/src/ansys/dpf/core/mesh_scoping_factory.py b/src/ansys/dpf/core/mesh_scoping_factory.py index f709c6f27f1..71e8f55a0d2 100644 --- a/src/ansys/dpf/core/mesh_scoping_factory.py +++ b/src/ansys/dpf/core/mesh_scoping_factory.py @@ -23,87 +23,101 @@ """ mesh_scoping_factory. -Contains functions to simplify creating mesh scopings. +Contains functions to simplify creating a mesh scoping. """ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: # pragma: nocover + from ansys.dpf.core.server_types import AnyServerType + from ansys.dpf.core.scoping import IdVectorType + from ansys.dpf.core.model import Model + from ansys.dpf.core import Scoping from ansys.dpf.core.common import locations -def nodal_scoping(node_ids, server=None): - """Create a specific nodal :class:`ansys.dpf.core.Scoping` associated with a mesh. +def nodal_scoping(node_ids: IdVectorType, server: AnyServerType = None) -> Scoping: + """Create a nodal :class:`ansys.dpf.core.Scoping` defining a list of node IDs. Parameters ---------- - node_ids : list[int] - List of IDs for the nodes. - server : DpfServer, optional + node_ids: + List of node IDs. + server: Server with the channel connected to the remote or local instance. The default is ``None``, in which case an attempt is made to use the global server. Returns ------- - scoping : Scoping + scoping: + A nodal scoping containing the node IDs provided. """ scoping = Scoping(server=server, ids=node_ids, location=locations.nodal) return scoping -def elemental_scoping(element_ids, server=None): - """Create a specific elemental :class:`ansys.dpf.core.Scoping` associated with a mesh. +def elemental_scoping(element_ids: IdVectorType, server: AnyServerType = None) -> Scoping: + """Create an elemental :class:`ansys.dpf.core.Scoping` defining a list of element IDs. Parameters ---------- - element_ids : list[int] - List of IDs for the elements. - server : DpfServer, optional + element_ids: + List of element IDs. + server: Server with the channel connected to the remote or local instance. The default is ``None``, in which case an attempt is made to use the global server. Returns ------- - scoping : Scoping + scoping: + An elemental scoping containing the element IDs provided. """ scoping = Scoping(server=server, ids=element_ids, location=locations.elemental) return scoping -def face_scoping(face_ids, server=None): - """Create a specific face :class:`ansys.dpf.core.Scoping` associated with a mesh. +def face_scoping(face_ids: IdVectorType, server: AnyServerType = None) -> Scoping: + """Create a face :class:`ansys.dpf.core.Scoping`defining a list of face IDs. Parameters ---------- - face_ids : list[int] - List of IDs for the faces. - server : DpfServer, optional + face_ids: + List of face IDs. + server: Server with the channel connected to the remote or local instance. The default is ``None``, in which case an attempt is made to use the global server. Returns ------- - scoping : Scoping + scoping: + A face scoping containing the face IDs provided. """ scoping = Scoping(server=server, ids=face_ids, location=locations.faces) return scoping -def named_selection_scoping(named_selection_name, model, server=None): - """Create a specific :class:`ansys.dpf.core.Scoping` associated with a specified model's mesh. +def named_selection_scoping( + named_selection_name: str, model: Model, server: AnyServerType = None +) -> Scoping: + """Create a :class:`ansys.dpf.core.Scoping` based on a named selection in a model. Parameters ---------- - named_selection_name : str + named_selection_name: Name of the named selection. - server : DpfServer, optional - Server with the channel connected to the remote or local instance. - The default is ``None``, in which case an attempt is made to use the - global server. + model: + Model where the named selection exists. Returns ------- - scoping : Scoping + scoping: + A scoping containing the IDs of the entities in the named selection. + The location depends on the type of entities targeted by the named selection. """ return model.metadata.named_selection(named_selection_name) diff --git a/src/ansys/dpf/core/scoping.py b/src/ansys/dpf/core/scoping.py index 8ee77b8461f..8b7729f695c 100644 --- a/src/ansys/dpf/core/scoping.py +++ b/src/ansys/dpf/core/scoping.py @@ -22,7 +22,9 @@ """Scoping.""" +from __future__ import annotations import traceback +from typing import Union, TYPE_CHECKING import warnings import ctypes @@ -41,9 +43,16 @@ dpf_vector_capi, dpf_vector_abstract_api, dpf_vector, - dpf_array, utils, ) +from ansys.dpf.gate.dpf_array import DPFArray + +if TYPE_CHECKING: # pragma: nocover + from ansys.dpf.core.server_types import AnyServerType + import ansys.grpc.dpf.scoping_pb2.Scoping as ScopingMessage + from ctypes import c_void_p as ScopingPointer + + IdVectorType = Union[list[int], range] class Scoping: @@ -51,32 +60,51 @@ class Scoping: Parameters ---------- - scoping : ctypes.c_void_p, ansys.grpc.dpf.scoping_pb2.Scoping message, optional - - server : DPFServer, optional + scoping: + gRPC message or pointer for an existing scoping on the server. + server: Server with channel connected to the remote or local instance. The default is ``None``, in which case an attempt is made to use the global server. + ids: + List of entity IDs. + location: + Location for this scoping. Defines the type of entities the IDs correspond to. + For example, if location is :py:attr:`locations.nodal`, + then the scoping is a list of node IDs. Examples -------- - Create a mesh scoping. + Create a scoping for mesh entities. >>> from ansys.dpf import core as dpf >>> # 1. using the mesh_scoping_factory >>> from ansys.dpf.core import mesh_scoping_factory >>> # a. scoping with elemental location that targets the elements with id 2, 7 and 11 >>> my_elemental_scoping = mesh_scoping_factory.elemental_scoping([2, 7, 11]) - >>> # b. scoping with nodal location that targets the elements with id 4 and 6 - >>> my_nodal_scoping = mesh_scoping_factory.nodal_scoping([4, 6]) - >>> #2. using the classic API - >>> my_scoping = dpf.Scoping() - >>> my_scoping.location = dpf.locations.nodal #optional - >>> my_scoping.ids = list(range(1,11)) + >>> # b. scoping with nodal location that targets the elements with id 4 to 6 + >>> my_nodal_scoping = mesh_scoping_factory.nodal_scoping(range(4, 7)) + >>> #2. using the Scoping class directly + >>> # a. scoping with elemental location that targets the elements with id 2, 7 and 11 + >>> my_elemental_scoping = dpf.Scoping(location=dpf.locations.elemental, ids=[2, 7, 11]) + >>> # b. scoping with nodal location that targets the elements with id 4 to 6 + >>> my_nodal_scoping = dpf.Scoping(ids=range(4, 7)) + >>> # 3. create a time_freq scoping that targets the second load step + >>> from ansys.dpf.core import time_freq_scoping_factory + >>> # a. using the time_freq_scoping_factory + >>> my_load_step_scoping = time_freq_scoping_factory.scoping_by_load_step(2) + >>> # b. using the Scoping class directly + >>> my_load_step_scoping = dpf.Scoping(location=dpf.locations.time_freq_step, ids=[2]) """ - def __init__(self, scoping=None, server=None, ids=None, location=None): + def __init__( + self, + scoping: Union[ScopingMessage, ScopingPointer] = None, + server: AnyServerType = None, + ids: IdVectorType = None, + location: locations = None, + ): """Initialize the scoping with an optional scoping message or by connecting to a stub.""" # step 1: get server self._server = server_module.get_or_create_server( @@ -152,18 +180,17 @@ def _set_location(self, loc=locations.nodal): self._api.scoping_set_location(self, loc) @version_requires("2.1") - def _set_ids(self, ids): + def _set_ids(self, ids: IdVectorType): """Set the ids. Parameters ---------- - ids : list of int - IDs to set. + ids: + Entity IDs to set. - Notes - ----- - Print a progress bar. """ + if isinstance(ids, range): + ids = list(ids) if isinstance(self._server, server_types.InProcessServer): self._api.scoping_resize(self, len(ids)) ids_ptr = self._api.scoping_get_ids(self, len(ids)) @@ -204,20 +231,36 @@ def _get_ids(self, np_array=None): self._api.scoping_get_ids_for_dpf_vector( self, vec, vec.internal_data, vec.internal_size ) - return dpf_array.DPFArray(vec) if np_array else vec.np_array.tolist() + return DPFArray(vec) if np_array else vec.np_array.tolist() except NotImplementedError: return self._api.scoping_get_ids(self, np_array) - def set_id(self, index, scopingid): - """Set the ID of a scoping's index. + def get_ids(self, np_array: bool = False) -> Union[list[int], DPFArray]: + """Return the list of entity IDs in the scoping as a list or as a numpy.ndarray. Parameters ---------- - index : int - Index of the scoping. - scopingid : int - ID of the scoping. + np_array: + Whether to return the list of IDs as a numpy array. + + Returns + ------- + ids: + List of entity IDs in the scoping. + + """ + return self._get_ids(np_array=np_array) + + def set_id(self, index: int, scopingid: int): + """Set the ID of the entity at index in the scoping. + + Parameters + ---------- + index: + Index in the scoping. + scopingid: + ID of the entity. """ self._api.scoping_set_entity(self, scopingid, index) @@ -251,73 +294,77 @@ def _get_index(self, scopingid): """ return self._api.scoping_index_by_id(self, scopingid) - def id(self, index: int): - """Retrieve the ID at a given index. + def id(self, index: int) -> int: + """Retrieve the entity ID at a given index. Parameters ---------- - index : int - Index for the ID. + index: + Index of the entity in the scoping. Returns ------- - size : int + entity_id: + ID of the entity at index in the scoping. """ return self._get_id(index) - def index(self, id: int): - """Retrieve the index of a given ID. + def index(self, id: int) -> int: + """Retrieve the index for a given entity ID. Parameters ---------- - id : int - ID for the index to retrieve. + id: + Entity ID at the index in the scoping. Returns ------- - size : int + index: + Index in the scoping for the entity ID. """ return self._get_index(id) @property - def ids(self): - """Retrieve a list of IDs in the scoping. + def ids(self) -> Union[DPFArray, list[int]]: + """Retrieve the entity IDs in the scoping. Returns ------- - ids : DPFArray, list of int - List of IDs to retrieve. By default a mutable DPFArray is returned, to change - the return type to a list for the complete python session, see + ids: + List of IDs in the scoping. + By default, a mutable DPFArray is returned. + To change the return type to a list for the complete python session, see :func:`ansys.dpf.core.settings.get_runtime_client_config` and :func:`ansys.dpf.core.runtime_config.RuntimeClientConfig.return_arrays`. - To change the return type to a list once, use - :func:`ansys.dpf.core.scoping.Scoping._get_ids` with the parameter ``np_array=False``. + To get the list of IDs from a scoping as a Python list without changing a default + configuration, use :func:`ansys.dpf.core.scoping.Scoping.get_ids` instead. - Notes - ----- - Print a progress bar. """ return self._get_ids() @ids.setter - def ids(self, value): + def ids(self, value: IdVectorType): self._set_ids(value) @property - def location(self): - """Location of the IDs as a string, such as ``"nodal"``, ``"elemental"``, and ``"time_freq"``. + def location(self) -> str: + """Location of the scoping as a string. + + This defines the type of entity the IDs correspond to (such as node ID, element ID, face ID, and so on). Returns ------- - location : str + location: + The location of the scoping. + One of the values of :class:`~ansys.dpf.core.common.locations`. """ return self._get_location() @location.setter - def location(self, value): + def location(self, value: locations): self._set_location(value) def __len__(self): @@ -362,12 +409,13 @@ def __setitem__(self, index, id): return self.set_id(index, id) @property - def size(self): + def size(self) -> int: """Length of the list of IDs. Returns ------- - size : int + size: + Size of the scoping. """ return self._count() diff --git a/tests/test_scoping.py b/tests/test_scoping.py index d8378ad15fa..90990aedebc 100644 --- a/tests/test_scoping.py +++ b/tests/test_scoping.py @@ -64,6 +64,18 @@ def test_set_get_ids_scoping(server_type): assert np.allclose(scop.ids, ids) +def test_set_get_ids_scoping_range(server_type): + range_ids = range(1, 10) + scop = Scoping( + ids=range_ids, + server=server_type, + ) + assert np.allclose(scop.ids, range_ids) + scop = Scoping(server=server_type) + scop.ids = range_ids + assert np.allclose(scop.ids, range_ids) + + @pytest.mark.skipif( not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_2_0, reason="Requires server version higher than 2.0", @@ -90,10 +102,10 @@ def test_get_ids_return_type_scoping(server_type): client_config.return_arrays = return_arrays_init assert np.allclose(scop.ids, ids) assert isinstance(scop.ids, np.ndarray) - assert np.allclose(scop._get_ids(True), ids) - assert isinstance(scop._get_ids(True), np.ndarray) - assert np.allclose(scop._get_ids(False), ids) - assert isinstance(scop._get_ids(False), list) + assert np.allclose(scop.get_ids(True), ids) + assert isinstance(scop.get_ids(True), np.ndarray) + assert np.allclose(scop.get_ids(False), ids) + assert isinstance(scop.get_ids(False), list) def test_get_location_scoping():