From 9be9bb822c1a5ec93409cf68357185bfca3a3e5a Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Fri, 21 Mar 2025 17:14:46 +0100 Subject: [PATCH 1/2] Add API functions for Just-in-time mapping co-authored-by: Benjamin Rodenberg --- cyprecice/Participant.pxd | 6 ++ cyprecice/cyprecice.pyx | 109 ++++++++++++++++++++++++++++++++++- test/Participant.cpp | 25 ++++++++ test/test_bindings_module.py | 9 +++ 4 files changed, 148 insertions(+), 1 deletion(-) diff --git a/cyprecice/Participant.pxd b/cyprecice/Participant.pxd index 7e157f6a..51bf9571 100644 --- a/cyprecice/Participant.pxd +++ b/cyprecice/Participant.pxd @@ -73,6 +73,12 @@ cdef extern from "precice/precice.hpp" namespace "precice": void readData (const string& meshName, const string& dataName, vector[int] vertices, const double relativeReadTime, vector[double]& values) except + + # Just-in-time mapping + + void writeAndMapData (const string& meshName, const string& dataName, vector[double] coordinates, vector[double] values) except + + + void mapAndReadData (const string& meshName, const string& dataName, vector[double] coordinates, double relativeReadTime, vector[double]& values) except + + # direct access void setMeshAccessRegion (const string& meshName, vector[double] boundingBox) except + diff --git a/cyprecice/cyprecice.pyx b/cyprecice/cyprecice.pyx index f7a598d3..c2519478 100644 --- a/cyprecice/cyprecice.pyx +++ b/cyprecice/cyprecice.pyx @@ -830,7 +830,7 @@ cdef class Participant: mesh_name : str Name of the mesh to write to. data_name : str - ID to read from. + Name of the data to read from. vertex_ids : array_like Indices of the vertices. relative_read_time : double @@ -903,6 +903,113 @@ cdef class Participant: else: return np_values.reshape((size, dimensions)) + def write_and_map_data (self, mesh_name, data_name, coordinates, values): + """ + This function writes values at temporary locations to data of a mesh. + As opposed to the writeData function using VertexIDs, this function allows to write data via coordinates, + which don't have to be specified during the initialization. This is particularly useful for meshes, which + vary over time. Note that using this function comes at a performance cost, since the specified mapping + needs to be computed locally for the given locations, whereas the other variant (writeData) can typically + exploit the static interface mesh and pre-compute data structures more efficiently. + + Values are passed identically to write_data. + + Parameters + ---------- + mesh_name : str + name of the mesh to write to. + data_name : str + Data name to write to. + coordinates : array_like + The coordinates of the vertices in a numpy array [N x D] where + N = number of vertices and D = dimensions of geometry. + values : array_like + Values of data + + Examples + -------- + Write scalar data for a 2D problem with 5 vertices: + >>> mesh_name = "MeshOne" + >>> data_name = "DataOne" + >>> coordinates = np.array([[c1_x, c1_y], [c2_x, c2_y], [c3_x, c3_y], [c4_x, c4_y], [c5_x, c5_y]]) + >>> values = np.array([v1, v2, v3, v4, v5]) + >>> participant.write_and_map_data(mesh_name, data_name, coordinates, values) + """ + + check_array_like(coordinates, "coordinates", "write_and_map_data") + check_array_like(values, "values", "write_and_map_data") + + if not isinstance(coordinates, np.ndarray): + coordinates = np.asarray(coordinates) + + if not isinstance(values, np.ndarray): + values = np.asarray(values) + + cdef vector[double] cpp_coordinates = coordinates.flatten() + cdef vector[double] cpp_values = values.flatten() + + self.thisptr.writeAndMapData (convert(mesh_name), convert(data_name), cpp_coordinates, cpp_values) + + def map_and_read_data (self, mesh_name, data_name, coordinates, relative_read_time): + """ + This function reads values at temporary locations from data of a mesh. + As opposed to the readData function using VertexIDs, this function allows reading data via coordinates, + which don't have to be specified during the initialization. This is particularly useful for meshes, which + vary over time. Note that using this function comes at a performance cost, since the specified mapping + needs to be computed locally for the given locations, whereas the other variant (readData) can typically + exploit the static interface mesh and pre-compute data structures more efficient. + + Values are read identically to read_data. + + Parameters + ---------- + mesh_name : str + Name of the mesh to write to. + data_name : str + Name of the data to read from. + coordinates : array_like + Coordinates of the vertices. + relative_read_time : double + Point in time where data is read relative to the beginning of the current time step + + Returns + ------- + values : numpy.ndarray + Contains the read data. + + Examples + -------- + Read scalar data for a 2D problem with 2 vertices: + >>> mesh_name = "MeshOne" + >>> data_name = "DataOne" + >>> coordinates = [(1.0, 1.0), (2.0, 2.0)] + >>> dt = 1.0 + >>> values = map_and_read_data(mesh_name, data_name, coordinates, dt) + >>> values.shape + >>> (2, ) + """ + + check_array_like(coordinates, "coordinates", "map_and_read_data") + + if not isinstance(coordinates, np.ndarray): + coordinates = np.asarray(coordinates) + + size = coordinates.shape[0] + dimensions = self.get_data_dimensions(mesh_name, data_name) + + cdef vector[double] cpp_coordinates = coordinates + cdef vector[double] cpp_values = [-1 for _ in range(size * dimensions)] + + self.thisptr.mapAndReadData (convert(mesh_name), convert(data_name), cpp_coordinates, relative_read_time, cpp_values) + + cdef np.ndarray[double, ndim=1] np_values = np.array(cpp_values, dtype=np.double) + + if len(coordinates) == 0: + return np_values.reshape((size)) + elif self.get_data_dimensions(mesh_name, data_name) == 1: + return np_values.reshape((size)) + else: + return np_values.reshape((size, dimensions)) def write_gradient_data (self, mesh_name, data_name, vertex_ids, gradients): """ diff --git a/test/Participant.cpp b/test/Participant.cpp index 03b4741e..819dc10f 100644 --- a/test/Participant.cpp +++ b/test/Participant.cpp @@ -278,6 +278,31 @@ void Participant:: readData } } +void Participant:: writeAndMapData +( + precice::string_view meshName, + precice::string_view dataName, + precice::span coordinates, + precice::span values) +{ + fake_read_write_buffer.clear(); + + for(const double value: values) { + fake_read_write_buffer.push_back(value); + } +} + +void Participant:: mapAndReadData +( + precice::string_view meshName, + precice::string_view dataName, + precice::span coordinates, + double relativeReadTime, + precice::span values) const +{ + std::copy(fake_read_write_buffer.begin(), fake_read_write_buffer.end(), values.begin()); +} + void Participant:: setMeshAccessRegion ( precice::string_view meshName, diff --git a/test/test_bindings_module.py b/test/test_bindings_module.py index 1f081f67..89975b0b 100644 --- a/test/test_bindings_module.py +++ b/test/test_bindings_module.py @@ -320,6 +320,15 @@ def test_read_write_vector_data_non_contiguous(self): read_data = participant.read_data("FakeMesh", "FakeVectorData", [0], dt) self.assertTrue(np.array_equal(write_data, read_data)) + def test_jit_mapping(self): + participant = precice.Participant("test", "dummy.xml", 0, 1) + write_data = [1, 2, 3] + participant.write_and_map_data("FakeMesh", "FakeScalarData", [0, 1, 2], write_data) + dt = 1 + read_data = participant.map_and_read_data("FakeMesh", "FakeScalarData", [0, 1, 2], dt) + print(f"{read_data=},{write_data=}") + self.assertTrue(np.array_equal(write_data, read_data)) + def test_get_version_information(self): version_info = precice.get_version_information() fake_version_info = b"dummy" # compare to test/SolverInterface.cpp From 442e89deabec38e8815b75b93340edbc90341cf6 Mon Sep 17 00:00:00 2001 From: Gerasimos Chourdakis Date: Mon, 24 Mar 2025 16:04:19 +0100 Subject: [PATCH 2/2] Remove debugging leftovers Co-authored-by: Ishaan Desai --- test/test_bindings_module.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_bindings_module.py b/test/test_bindings_module.py index 89975b0b..ee2287ee 100644 --- a/test/test_bindings_module.py +++ b/test/test_bindings_module.py @@ -326,7 +326,6 @@ def test_jit_mapping(self): participant.write_and_map_data("FakeMesh", "FakeScalarData", [0, 1, 2], write_data) dt = 1 read_data = participant.map_and_read_data("FakeMesh", "FakeScalarData", [0, 1, 2], dt) - print(f"{read_data=},{write_data=}") self.assertTrue(np.array_equal(write_data, read_data)) def test_get_version_information(self):