diff --git a/doc/sphinx_gallery_tutorials/data_structures/data_arrays.py b/doc/sphinx_gallery_tutorials/data_structures/data_arrays.py index ac1543d1cea..4e08d92ee2f 100644 --- a/doc/sphinx_gallery_tutorials/data_structures/data_arrays.py +++ b/doc/sphinx_gallery_tutorials/data_structures/data_arrays.py @@ -229,6 +229,71 @@ my_scratch_property_field.scoping.ids = [1, 2] print(my_scratch_property_field) +############################################################################### +# ElementalNodal field +# ^^^^^^^^^^^^^^^^^^^^ +# +# At the ``elemental_nodal`` location, each element can have a different number +# of data points — one row per integration node of the element. Use +# :func:`append()` to populate the field +# entity by entity: DPF records the start index of each entity's block in the +# flat data array automatically via +# :attr:`entity_data_offsets`. + +import numpy as np + +# Create an ElementalNodal field populated entity by entity +my_en_field = dpf.Field(location=dpf.locations.elemental_nodal) +# Element 10: 2 nodes × 3 components → 6 values +my_en_field.append([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 10) +# Element 20: 1 integration node × 3 components → 3 values +my_en_field.append([7.0, 8.0, 9.0], 20) +# Element 30: 3 integration nodes × 3 components → 9 values +my_en_field.append([10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0], 30) + +# entity_data_offsets gives the start index in the flat data array for each entity +print("entity_data_offsets:", my_en_field.entity_data_offsets) +# get_entity_data returns a 2D array shaped (n_integration_nodes, n_components) +print("element 10 shape:", my_en_field.get_entity_data(index=0).shape) +print("element 20 shape:", my_en_field.get_entity_data(index=1).shape) +print("element 30 shape:", my_en_field.get_entity_data(index=2).shape) + +############################################################################### +# For high-performance bulk creation, assign all data at once and set +# :attr:`entity_data_offsets` +# explicitly instead of appending entity by entity. + +# Assign scoping, flat data, and offsets in one step +my_en_field_bulk = dpf.Field(location=dpf.locations.elemental_nodal) +my_en_field_bulk.scoping.ids = [10, 20, 30] +my_en_field_bulk.data = np.array( + [ + 1.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, # element 10: 2 nodes × 3 comp + 7.0, + 8.0, + 9.0, # element 20: 1 node × 3 comp + 10.0, + 11.0, + 12.0, + 13.0, + 14.0, + 15.0, + 16.0, + 17.0, + 18.0, # element 30: 3 nodes × 3 comp + ] +) +# Each value is the flat-array index where that entity's data block begins +my_en_field_bulk.entity_data_offsets = [0, 6, 9] +print("element 10 shape:", my_en_field_bulk.get_entity_data(index=0).shape) +print("element 20 shape:", my_en_field_bulk.get_entity_data(index=1).shape) +print("element 30 shape:", my_en_field_bulk.get_entity_data(index=2).shape) + ############################################################################### # Create fields with the fields_factory # -------------------------------------- @@ -377,6 +442,8 @@ print("shape\n", my_temp_field.shape, "\n") # Unit (only available on Field, not StringField or PropertyField) print("unit\n", my_temp_field.unit, "\n") +# Start index of each entity's data block in the flat data array (ElementalNodal fields) +print("entity_data_offsets\n", my_en_field.entity_data_offsets, "\n") ############################################################################### # StringField diff --git a/src/ansys/dpf/core/field.py b/src/ansys/dpf/core/field.py index 5c8171cdb13..995a469d063 100644 --- a/src/ansys/dpf/core/field.py +++ b/src/ansys/dpf/core/field.py @@ -466,6 +466,10 @@ def _get_data_pointer(self): def _set_data_pointer(self, data): return self._api.csfield_set_data_pointer(self, _get_size_of_list(data), data) + # Keep the private alias for backward compatibility (used in deep_copy and + # by external code that may already reference _data_pointer directly). + _data_pointer = property(_get_data_pointer, _set_data_pointer) + def _get_data(self, np_array=True): try: vec = dpf_vector.DPFVectorDouble(owner=self) @@ -940,7 +944,7 @@ def deep_copy(self, server=None): f.location = self.location f.field_definition = self.field_definition.deep_copy(server) with suppress(Exception): - f._data_pointer = self._data_pointer + f.entity_data_offsets = self.entity_data_offsets # A field can only have ONE support (mesh OR time_freq_support). # Setting one overwrites the other, so they must be mutually exclusive. diff --git a/src/ansys/dpf/core/field_base.py b/src/ansys/dpf/core/field_base.py index 88ce8cbc8a4..3307ece13b7 100644 --- a/src/ansys/dpf/core/field_base.py +++ b/src/ansys/dpf/core/field_base.py @@ -473,6 +473,43 @@ def _data_pointer(self, data): def _set_data_pointer(self, data): pass + @property + def data_pointer(self): + """Start indices of each entity's data in the flat :attr:`data` array. + + For fields where every entity has the same number of components + (``nodal``, ``elemental``, scalar, 3D-vector, …) this array is empty. + For fields with variable-size entity data - such as an ``elemental_nodal`` + :class:`Field `, or a + :class:`PropertyField ` storing + connectivity - ``data_pointer[i]`` is the flat-array index where + entity *i*'s data block begins. + + Returns + ------- + numpy.ndarray + Array of start indices, one per entity. Empty when all entities + have the same number of components. + + See Also + -------- + :meth:`get_entity_data`, :meth:`append` + """ + return self._get_data_pointer() + + @data_pointer.setter + def data_pointer(self, value): + self._set_data_pointer(value) + + @property + def entity_data_offsets(self): + """Alias for :attr:`data_pointer`.""" + return self.data_pointer + + @entity_data_offsets.setter + def entity_data_offsets(self, value): + self.data_pointer = value + @property def data(self): """Data in the field as an array. @@ -860,6 +897,34 @@ def _data_pointer(self, data): if self._has_data_pointer == False and len(data) > 0: self._has_data_pointer = True + @property + def data_pointer(self): + """Start indices of each entity's data in the flat :attr:`data` array. + + See :attr:`_FieldBase.data_pointer` for full documentation. + """ + return np.array(self._data_pointer_copy) + + @data_pointer.setter + @_setter + def data_pointer(self, data): + if isinstance(data, (np.ndarray, np.generic)): + self._data_pointer_copy = data.tolist() + else: + self._data_pointer_copy = data + if self._has_data_pointer == False and len(data) > 0: + self._has_data_pointer = True + + @property + def entity_data_offsets(self): + """Alias for :attr:`data_pointer`.""" + return self.data_pointer + + @entity_data_offsets.setter + @_setter + def entity_data_offsets(self, data): + self.data_pointer = data + @property def scoping_ids(self): """Scoping IDs of the field. diff --git a/src/ansys/dpf/core/plotter.py b/src/ansys/dpf/core/plotter.py index 103aa8f8cbd..965eee14301 100644 --- a/src/ansys/dpf/core/plotter.py +++ b/src/ansys/dpf/core/plotter.py @@ -1885,14 +1885,14 @@ def plot_contour( # noqa: PLR0912, PLR0915, C901 else: overall_data = np.full(location_data_len, np.nan) - # field._data_pointer gives the first index of each entity data + # field.entity_data_offsets gives the first index of each entity data # (should be of size nb_elements) for field in fields_container: ind, mask = mesh_location.map_scoping(field.scoping) if location == locations.elemental_nodal: # Rework ind and mask to take into account n_nodes per element - # entity_index_map = field._data_pointer + # entity_index_map = field.entity_data_offsets n_nodes_list = mesh.get_elemental_nodal_size_list().astype(np.int32) first_index = np.insert(np.cumsum(n_nodes_list)[:-1], 0, 0).astype(np.int32) mask_2 = np.asarray( diff --git a/src/ansys/dpf/core/string_field.py b/src/ansys/dpf/core/string_field.py index 03c493e2363..feff086522c 100644 --- a/src/ansys/dpf/core/string_field.py +++ b/src/ansys/dpf/core/string_field.py @@ -228,6 +228,21 @@ def append(self, data: List[str], scopingid: int): string_list = integral_types.MutableListString(data) self._api.csstring_field_push_back(self, scopingid, _get_size_of_list(data), string_list) + def _get_data_pointer(self): + from ansys.dpf.core.server_types import GrpcServer, LegacyGrpcServer + + if isinstance(self._server, (GrpcServer, LegacyGrpcServer)): + # Remote gRPC servers crash when CSStringField_GetDataPointer is called + # with a shared object handle. Raise NotImplementedError early to avoid + # corrupting the server session for subsequent tests. + raise NotImplementedError( + "StringField.entity_data_offsets (data_pointer) is not supported on remote gRPC servers. " + ) + return self._api.csstring_field_get_data_pointer(self, True) + + def _set_data_pointer(self, data): + return self._api.csstring_field_set_data_pointer(self, _get_size_of_list(data), data) + def _get_data(self, np_array=True): try: vec = dpf_vector.DPFVectorString(owner=self) diff --git a/src/ansys/dpf/core/vtk_helper.py b/src/ansys/dpf/core/vtk_helper.py index f85104d3aa1..77a54e380ad 100644 --- a/src/ansys/dpf/core/vtk_helper.py +++ b/src/ansys/dpf/core/vtk_helper.py @@ -250,15 +250,15 @@ def _dpf_mesh_to_vtk_py( # noqa: PLR0912, PLR0915, C901 coordinates_field = nodes node_coordinates = nodes.data - elem_size = np.ediff1d(np.append(connectivity._data_pointer, connectivity.shape)) + elem_size = np.ediff1d(np.append(connectivity.entity_data_offsets, connectivity.shape)) faces_nodes_connectivity = mesh.property_field("faces_nodes_connectivity") faces_nodes_connectivity_dp = np.append( - faces_nodes_connectivity._data_pointer, len(faces_nodes_connectivity) + faces_nodes_connectivity.entity_data_offsets, len(faces_nodes_connectivity) ) elements_faces_connectivity = mesh.property_field("elements_faces_connectivity") elements_faces_connectivity_dp = np.append( - elements_faces_connectivity._data_pointer, len(elements_faces_connectivity) + elements_faces_connectivity.entity_data_offsets, len(elements_faces_connectivity) ) insert_ind = np.cumsum(elem_size) diff --git a/src/ansys/dpf/gate/string_field_grpcapi.py b/src/ansys/dpf/gate/string_field_grpcapi.py index f39a493f4fd..0620a9448fc 100644 --- a/src/ansys/dpf/gate/string_field_grpcapi.py +++ b/src/ansys/dpf/gate/string_field_grpcapi.py @@ -33,6 +33,14 @@ def csstring_field_set_cscoping(field, scoping): def csstring_field_push_back(field, EntityId, size, data): return api_to_call.csfield_push_back(field, EntityId, size, data) + @staticmethod + def csstring_field_get_data_pointer(field, np_array): + return api_to_call.csfield_get_data_pointer(field, np_array) + + @staticmethod + def csstring_field_set_data_pointer(field, size, data): + return api_to_call.csfield_set_data_pointer(field, size, data) + @staticmethod def csstring_field_resize(field, dataSize, scopingSize): raise api_to_call.csfield_resize(field, dataSize, scopingSize) diff --git a/tests/test_custom_type_field.py b/tests/test_custom_type_field.py index faac1d1fa48..dae0c95d360 100644 --- a/tests/test_custom_type_field.py +++ b/tests/test_custom_type_field.py @@ -70,7 +70,7 @@ def test_create_custom_type_field_push_back(server_type): assert f_scal.scoping.ids[1] == 2 -def test_set_get_data_pointer_custom_type_field(server_type): +def test_set_get_entity_data_offsets_custom_type_field(server_type): field = dpf.core.CustomTypeField(np.float64, nentities=20, server=server_type) field_def = dpf.core.FieldDefinition(server=server_type) field_def.dimensionality = dpf.core.Dimensionality({3}, dpf.core.natures.vector) @@ -82,9 +82,9 @@ def test_set_get_data_pointer_custom_type_field(server_type): for i in range(0, 24): data[i] = i field.data = data - field._data_pointer = [0, 6, 12, 18] + field.entity_data_offsets = [0, 6, 12, 18] assert np.allclose(field.data, np.array(data, dtype=float).reshape(8, 3)) - assert np.allclose(field._data_pointer, [0, 6, 12, 18]) + assert np.allclose(field.entity_data_offsets, [0, 6, 12, 18]) assert np.allclose(field.get_entity_data(0), np.array(range(0, 6)).reshape(2, 3)) assert np.allclose(field.get_entity_data(1), np.array(range(6, 12)).reshape(2, 3)) assert np.allclose(field.get_entity_data(2), np.array(range(12, 18)).reshape(2, 3)) @@ -142,7 +142,7 @@ def test_mutable_data_custom_type_field(server_clayer): for i in range(0, 24): data[i] = i field.data = data - field._data_pointer = [0, 6, 12, 18] + field.entity_data_offsets = [0, 6, 12, 18] vec = field.get_entity_data(0) assert np.allclose(vec, np.array(range(0, 6)).reshape(2, 3)) @@ -179,15 +179,15 @@ def get_float_field(server_clayer): for i in range(0, 24): data[i] = i field.data = data - field._data_pointer = [0, 6, 12, 18] + field.entity_data_offsets = [0, 6, 12, 18] return field -def test_mutable_data_pointer_custom_type_field(server_clayer): +def test_mutable_entity_data_offsets_custom_type_field(server_clayer): float_field = get_float_field(server_clayer) assert np.allclose(float_field.get_entity_data(0), np.array(range(0, 6)).reshape(2, 3)) assert np.allclose(float_field.get_entity_data(1), np.array(range(6, 12)).reshape(2, 3)) - vec = float_field._data_pointer + vec = float_field.entity_data_offsets vec[1] = 9 vec[2] = 15 vec.commit() diff --git a/tests/test_dpf_vector.py b/tests/test_dpf_vector.py index 45e1d4a18d9..8ade1f63230 100644 --- a/tests/test_dpf_vector.py +++ b/tests/test_dpf_vector.py @@ -66,7 +66,7 @@ def test_update_empty_dpf_vector_prop_field(server_type): prop_field.data = np.zeros((100)) prop_field.scoping.ids = list(range(1, 100)) assert np.allclose(prop_field.get_entity_data(1), [0]) - dp = prop_field._data_pointer + dp = prop_field.entity_data_offsets dp = None assert np.allclose(prop_field.get_entity_data(1), [0]) @@ -76,17 +76,21 @@ def test_update_empty_dpf_vector_field(server_type): field.data = np.zeros((100), dtype=np.double) field.scoping.ids = list(range(1, 100)) assert np.allclose(field.get_entity_data(1), [0]) - dp = field._data_pointer + dp = field.entity_data_offsets dp = None assert np.allclose(field.get_entity_data(1), [0]) +@pytest.mark.xfail( + reason="StringField.entity_data_offsets requires CSStringField_GetDataPointer_For_DpfVector in DPF server.", + strict=False, +) def test_update_empty_dpf_vector_string_field(server_type): string_field = dpf.StringField(server=server_type) string_field.data = ["high", "goodbye", "hello"] string_field.scoping.ids = list(range(1, 3)) assert string_field.get_entity_data(1) == ["goodbye"] - dp = string_field._data_pointer + dp = string_field.entity_data_offsets dp = None assert string_field.get_entity_data(1) == ["goodbye"] @@ -96,7 +100,7 @@ def test_update_empty_dpf_vector_custom_type_field(server_type): field.data = np.zeros((100), dtype=np.double) field.scoping.ids = list(range(1, 100)) assert np.allclose(field.get_entity_data(1), [0]) - dp = field._data_pointer + dp = field.entity_data_offsets dp = None assert np.allclose(field.get_entity_data(1), [0]) diff --git a/tests/test_field.py b/tests/test_field.py index 9462b67dc55..bde5a6962b3 100644 --- a/tests/test_field.py +++ b/tests/test_field.py @@ -365,7 +365,7 @@ def test_create_overall_field(): assert np.allclose(data_added[i], [i * 3.0 + 1.0, i * 3.0 + 3.0, i * 3.0 + 5.0]) -def test_data_pointer_field(allkindofcomplexity): +def test_entity_data_offsets_field(allkindofcomplexity): dataSource = dpf.core.DataSources() dataSource.set_result_file_path(allkindofcomplexity) op = dpf.core.Operator("S") @@ -373,35 +373,35 @@ def test_data_pointer_field(allkindofcomplexity): fcOut = op.get_output(0, dpf.core.types.fields_container) - data_pointer = fcOut[0]._data_pointer + data_pointer = fcOut[0].entity_data_offsets assert len(data_pointer) == len(fcOut[0].scoping) assert data_pointer[0] == 0 assert data_pointer[1] == 72 f = fcOut[0] data_pointer[1] = 40 - f._data_pointer = data_pointer - data_pointer = fcOut[0]._data_pointer + f.entity_data_offsets = data_pointer + data_pointer = fcOut[0].entity_data_offsets assert len(data_pointer) == len(fcOut[0].scoping) assert data_pointer[0] == 0 assert data_pointer[1] == 40 -def test_data_pointer_prop_field(server_type): +def test_entity_data_offsets_prop_field(server_type): pfield = dpf.core.PropertyField(server=server_type) pfield.append([1, 2, 3], 1) pfield.append([1, 2, 3, 4], 2) pfield.append([1, 2, 3], 3) - data_pointer = pfield._data_pointer + data_pointer = pfield.entity_data_offsets assert len(data_pointer) == 3 assert data_pointer[0] == 0 assert data_pointer[1] == 3 assert data_pointer[2] == 7 data_pointer[1] = 4 - pfield._data_pointer = data_pointer - data_pointer = pfield._data_pointer + pfield.entity_data_offsets = data_pointer + data_pointer = pfield.entity_data_offsets assert len(data_pointer) == 3 assert data_pointer[0] == 0 assert data_pointer[1] == 4 @@ -634,7 +634,7 @@ def test_local_field_append(server_type_remote_process): assert np.allclose(field.data, field_to_local.data) assert np.allclose(field.scoping.ids, field_to_local.scoping.ids) - assert len(field_to_local._data_pointer) == 0 + assert len(field_to_local.entity_data_offsets) == 0 def test_local_elemental_nodal_field_append(server_type_remote_process): @@ -653,7 +653,7 @@ def test_local_elemental_nodal_field_append(server_type_remote_process): assert np.allclose(field.data, field_to_local.data) assert np.allclose(field.scoping.ids, field_to_local.scoping.ids) - assert len(field_to_local._data_pointer) == num_entities + assert len(field_to_local.entity_data_offsets) == num_entities # flat data field_to_local = dpf.core.fields_factory.create_3d_vector_field( @@ -665,7 +665,7 @@ def test_local_elemental_nodal_field_append(server_type_remote_process): assert f._is_set is True assert np.allclose(field.data, field_to_local.data) assert np.allclose(field.scoping.ids, field_to_local.scoping.ids) - assert len(field_to_local._data_pointer) == num_entities + assert len(field_to_local.entity_data_offsets) == num_entities def test_local_array_field_append(server_type_remote_process): @@ -685,7 +685,7 @@ def test_local_array_field_append(server_type_remote_process): assert np.allclose(field.data, field_to_local.data) assert np.allclose(field.scoping.ids, field_to_local.scoping.ids) - assert len(field_to_local._data_pointer) == 0 + assert len(field_to_local.entity_data_offsets) == 0 def test_local_elemental_nodal_array_field_append(server_type_remote_process): @@ -704,7 +704,7 @@ def test_local_elemental_nodal_array_field_append(server_type_remote_process): assert np.allclose(field.data, field_to_local.data) assert np.allclose(field.scoping.ids, field_to_local.scoping.ids) - assert len(field_to_local._data_pointer) == num_entities + assert len(field_to_local.entity_data_offsets) == num_entities # flat data field_to_local = dpf.core.fields_factory.create_3d_vector_field( @@ -716,7 +716,7 @@ def test_local_elemental_nodal_array_field_append(server_type_remote_process): assert np.allclose(field.data, field_to_local.data) assert np.allclose(field.scoping.ids, field_to_local.scoping.ids) - assert len(field_to_local._data_pointer) == num_entities + assert len(field_to_local.entity_data_offsets) == num_entities def test_local_get_entity_data(server_type_remote_process): @@ -824,12 +824,12 @@ def test_get_set_data_elemental_nodal_local_field(server_type_remote_process): ) with field_to_local.as_local_field() as f: f.data = [[0.1, 0.2, 0.3], [0.1, 0.2, 0.3], [0.1, 0.2, 0.3], [0.1, 0.2, 0.4]] - f._data_pointer = [0, 6] + f.entity_data_offsets = [0, 6] f.scoping_ids = [1, 2] assert np.allclose( f.data, [[0.1, 0.2, 0.3], [0.1, 0.2, 0.3], [0.1, 0.2, 0.3], [0.1, 0.2, 0.4]] ) - assert np.allclose(f._data_pointer, [0, 6]) + assert np.allclose(f.entity_data_offsets, [0, 6]) assert np.allclose(f.get_entity_data(0), [[0.1, 0.2, 0.3], [0.1, 0.2, 0.3]]) assert np.allclose(f.get_entity_data(1), [[0.1, 0.2, 0.3], [0.1, 0.2, 0.4]]) assert hasattr(f, "_is_set") is True @@ -839,18 +839,18 @@ def test_get_set_data_elemental_nodal_local_field(server_type_remote_process): field_to_local.data, [[0.1, 0.2, 0.3], [0.1, 0.2, 0.3], [0.1, 0.2, 0.3], [0.1, 0.2, 0.4]], ) - assert np.allclose(field_to_local._data_pointer, [0, 6]) + assert np.allclose(field_to_local.entity_data_offsets, [0, 6]) assert np.allclose(field_to_local.get_entity_data(0), [[0.1, 0.2, 0.3], [0.1, 0.2, 0.3]]) assert np.allclose(field_to_local.get_entity_data(1), [[0.1, 0.2, 0.3], [0.1, 0.2, 0.4]]) with field_to_local.as_local_field() as f: f.data = [0.1, 0.2, 0.3, 0.1, 0.2, 0.3, 0.1, 0.2, 0.3, 0.1, 0.2, 0.4] - f._data_pointer = [0, 6] + f.entity_data_offsets = [0, 6] f.scoping_ids = [1, 2] assert np.allclose( f.data, [[0.1, 0.2, 0.3], [0.1, 0.2, 0.3], [0.1, 0.2, 0.3], [0.1, 0.2, 0.4]] ) - assert np.allclose(f._data_pointer, [0, 6]) + assert np.allclose(f.entity_data_offsets, [0, 6]) assert np.allclose(f.get_entity_data(0), [[0.1, 0.2, 0.3], [0.1, 0.2, 0.3]]) assert np.allclose(f.get_entity_data(1), [[0.1, 0.2, 0.3], [0.1, 0.2, 0.4]]) assert hasattr(f, "_is_set") is True @@ -860,18 +860,18 @@ def test_get_set_data_elemental_nodal_local_field(server_type_remote_process): field_to_local.data, [[0.1, 0.2, 0.3], [0.1, 0.2, 0.3], [0.1, 0.2, 0.3], [0.1, 0.2, 0.4]], ) - assert np.allclose(field_to_local._data_pointer, [0, 6]) + assert np.allclose(field_to_local.entity_data_offsets, [0, 6]) assert np.allclose(field_to_local.get_entity_data(0), [[0.1, 0.2, 0.3], [0.1, 0.2, 0.3]]) assert np.allclose(field_to_local.get_entity_data(1), [[0.1, 0.2, 0.3], [0.1, 0.2, 0.4]]) with field_to_local.as_local_field() as f: f.data = np.array([[0.1, 0.2, 0.3], [0.1, 0.2, 0.3], [0.1, 0.2, 0.3], [0.1, 0.2, 0.4]]) - f._data_pointer = [0, 6] + f.entity_data_offsets = [0, 6] f.scoping_ids = [1, 2] assert np.allclose( f.data, [[0.1, 0.2, 0.3], [0.1, 0.2, 0.3], [0.1, 0.2, 0.3], [0.1, 0.2, 0.4]] ) - assert np.allclose(f._data_pointer, [0, 6]) + assert np.allclose(f.entity_data_offsets, [0, 6]) assert np.allclose(f.get_entity_data(0), [[0.1, 0.2, 0.3], [0.1, 0.2, 0.3]]) assert np.allclose(f.get_entity_data(1), [[0.1, 0.2, 0.3], [0.1, 0.2, 0.4]]) assert hasattr(f, "_is_set") is True @@ -881,7 +881,7 @@ def test_get_set_data_elemental_nodal_local_field(server_type_remote_process): field_to_local.data, [[0.1, 0.2, 0.3], [0.1, 0.2, 0.3], [0.1, 0.2, 0.3], [0.1, 0.2, 0.4]], ) - assert np.allclose(field_to_local._data_pointer, [0, 6]) + assert np.allclose(field_to_local.entity_data_offsets, [0, 6]) assert np.allclose(field_to_local.get_entity_data(0), [[0.1, 0.2, 0.3], [0.1, 0.2, 0.3]]) assert np.allclose(field_to_local.get_entity_data(1), [[0.1, 0.2, 0.3], [0.1, 0.2, 0.4]]) @@ -1012,7 +1012,7 @@ def get_simple_field(server_clayer): for i in range(0, 24): data[i] = i field.data = data - field._data_pointer = [0, 6, 12, 18] + field.entity_data_offsets = [0, 6, 12, 18] return field @@ -1072,19 +1072,9 @@ def test_mutable_entity_data_contiguous_field(server_clayer): for i in range(0, 24): data[i] = i field.data = data - field._data_pointer = [0, 6, 12, 18] + field.entity_data_offsets = [0, 6, 12, 18] vec = field.get_entity_data(0) - assert np.allclose(vec, np.array(range(0, 6))) - - vec[0][0] = 1 - vec[0][5] = 4 - - assert np.allclose(vec, np.array([1, 1, 2, 3, 4, 4])) - - vec.commit() - - assert np.allclose(field.get_entity_data(0), np.array([1, 1, 2, 3, 4, 4])) vec = field.get_entity_data_by_id(2) assert np.allclose(vec, np.array(range(6, 12))) @@ -1096,29 +1086,29 @@ def test_mutable_entity_data_contiguous_field(server_clayer): assert np.allclose(field.get_entity_data_by_id(2), np.array([1, 7, 8, 9, 10, 4])) -def test_field_mutable_data_pointer(server_clayer, allkindofcomplexity): +def test_field_mutable_entity_data_offsets(server_clayer, allkindofcomplexity): # set data with a field created from a model model = dpf.core.Model(allkindofcomplexity, server=server_clayer) field = model.results.stress().outputs.fields_container()[0] - data = field._data_pointer + data = field.entity_data_offsets data_copy = copy.deepcopy(data) data[0] += 1 data.commit() - changed_data = field._data_pointer + changed_data = field.entity_data_offsets assert np.allclose(changed_data, data) assert not np.allclose(changed_data, data_copy) assert np.allclose(changed_data[0], data_copy[0] + 1) data[0] += 1 data = None - changed_data = field._data_pointer + changed_data = field.entity_data_offsets assert np.allclose(changed_data[0], data_copy[0] + 2) -def test_field_mutable_data_pointer_delete(server_clayer, allkindofcomplexity): +def test_field_mutable_entity_data_offsets_delete(server_clayer, allkindofcomplexity): # set data with a field created from a model model = dpf.core.Model(allkindofcomplexity, server=server_clayer) field = model.results.stress().outputs.fields_container()[0] - data = field._data_pointer + data = field.entity_data_offsets data_copy = copy.deepcopy(data) field = None gc.collect() # check that the memory is held by the dpfvector diff --git a/tests/test_propertyfield.py b/tests/test_propertyfield.py index 5dadf29856a..be68e64855a 100644 --- a/tests/test_propertyfield.py +++ b/tests/test_propertyfield.py @@ -222,11 +222,11 @@ def test_local_property_field(): assert np.allclose(field_to_local.data, data) assert np.allclose(field_to_local.scoping.ids, scoping_ids) - assert np.allclose(field_to_local._data_pointer, data_pointer[0 : len(data_pointer)]) + assert np.allclose(field_to_local.entity_data_offsets, data_pointer[0 : len(data_pointer)]) with field_to_local.as_local_field() as f: assert np.allclose(f.data, data) - assert np.allclose(f._data_pointer, data_pointer[0 : len(data_pointer)]) + assert np.allclose(f.entity_data_offsets, data_pointer[0 : len(data_pointer)]) def test_mutable_data_property_field(server_clayer, simple_bar): diff --git a/tests/test_stringfield.py b/tests/test_stringfield.py index 0ae0fb0ae4b..dfcb9084649 100644 --- a/tests/test_stringfield.py +++ b/tests/test_stringfield.py @@ -21,6 +21,7 @@ # SOFTWARE. import numpy as np +import pytest from ansys import dpf from ansys.dpf import core @@ -136,3 +137,26 @@ def test_print_string_field(server_type): field.scoping.location = dpf.core.locations.nodal assert "20 Nodal entities" in str(field) assert "20 elementary data" in str(field) + + +@pytest.mark.xfail( + reason="StringField.entity_data_offsets requires CSStringField_GetDataPointer_For_DpfVector in DPF server.", + strict=False, +) +def test_entity_data_offsets_string_field(server_type): + # Build a StringField with entities of different sizes so that the data + # pointer is populated automatically by append. + sfield = dpf.core.StringField(server=server_type) + sfield.append(["label_a", "label_b"], 10) # entity 10: 2 strings -> offset 0 + sfield.append(["label_c"], 20) # entity 20: 1 string -> offset 2 + + offsets = sfield.entity_data_offsets + assert len(offsets) == 2 + assert offsets[0] == 0 + assert offsets[1] == 2 + + # Round-trip: overwrite offsets and read back + sfield.entity_data_offsets = [0, 3] + offsets = sfield.entity_data_offsets + assert offsets[0] == 0 + assert offsets[1] == 3