diff --git a/CMakeLists.txt b/CMakeLists.txt index ea3c8ceb..eacdeb4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,7 +121,7 @@ add_test(NAME read_write_test COMMAND test-precice-aste WORKING_DIRECTORY "${CM # Detect and register examples as tests -set(_examples lci_2d lci_3d nn nng_scalar nng_vector mapping_tester mapping_tester_serial mapping_tester_runtime mapping_tester_conv replay_mode) +set(_examples lci_2d lci_3d nn nn_just_in_time nng_scalar nng_vector mapping_tester mapping_tester_serial mapping_tester_runtime mapping_tester_conv replay_mode) foreach(example IN LISTS _examples) add_test(NAME aste.example.${example}.setup diff --git a/examples/mapping_tester/setup-test.json b/examples/mapping_tester/setup-test.json index b61943d1..af49d539 100644 --- a/examples/mapping_tester/setup-test.json +++ b/examples/mapping_tester/setup-test.json @@ -33,8 +33,12 @@ "executor": "cpu", "executor-options": "" }, + "np": { + "kind": "nearest-projection" + }, "nn": { - "kind": "nearest-neighbor" + "kind": "nearest-neighbor", + "batch-size": "5" } } }, diff --git a/examples/nn/precice-config.xml b/examples/nn/precice-config.xml index 58a057c3..926adaf5 100644 --- a/examples/nn/precice-config.xml +++ b/examples/nn/precice-config.xml @@ -26,6 +26,9 @@ + diff --git a/examples/nn_just_in_time/clean.sh b/examples/nn_just_in_time/clean.sh new file mode 100755 index 00000000..e2feb93d --- /dev/null +++ b/examples/nn_just_in_time/clean.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -e -x + +rm -f -r precice-run +rm -f -r precice-profiling +rm -f profiling.json +rm -f trace.json +rm -f *.log +rm -f map*.vtk +rm -f fine_mesh_*.vtk diff --git a/examples/nn_just_in_time/precice-config.xml b/examples/nn_just_in_time/precice-config.xml new file mode 100644 index 00000000..e5d7f855 --- /dev/null +++ b/examples/nn_just_in_time/precice-config.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/nn_just_in_time/run.sh b/examples/nn_just_in_time/run.sh new file mode 100755 index 00000000..2044f154 --- /dev/null +++ b/examples/nn_just_in_time/run.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -e -x + +# Calculate franke function on fine mesh +precice-aste-evaluate -m ../fine_mesh.vtk -f "franke3d" -d "Franke Function" -o "fine_mesh_nn.vtk" + +# Map from the finer mesh to coarser mesh +precice-aste-run -v -p A --mesh fine_mesh_nn --data "Franke Function" & +precice-aste-run -v -p B --mesh ../coarse_mesh --output map_nn --data "InterpolatedData" --read-just-in-time 0 + +# Calculate statistics +precice-aste-evaluate -m map_nn.vtk -f "franke3d" -d difference --diffdata "InterpolatedData" --diff diff --git a/src/common.cpp b/src/common.cpp index 2645df2d..77afd78c 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -22,7 +22,7 @@ OptionMap getOptions(int argc, char *argv[]) "precice-aste-run will look for timeseries as well as distributed meshes (e.g. from preCICE exports) " "automatically and load them if required.")( "output", po::value(), - "Output file name.")("vector", po::bool_switch(), "Distinguish between vector valued data and scalar data")("verbose,v", po::bool_switch(), "Enable verbose output")("all,a", po::bool_switch(), "Enable output from secondary ranks"); + "Output file name.")("vector", po::bool_switch(), "Distinguish between vector valued data and scalar data")("verbose,v", po::bool_switch(), "Enable verbose output")("all,a", po::bool_switch(), "Enable output from secondary ranks")("read-just-in-time,jit", po::value(), "Instead of the conventional mapping in preCICE, make use of a just-in-time mapping, where values are read in batches of values."); po::variables_map vm; diff --git a/src/modes.cpp b/src/modes.cpp index f07994c7..03feba6a 100644 --- a/src/modes.cpp +++ b/src/modes.cpp @@ -146,6 +146,15 @@ void aste::runMapperMode(const aste::ExecutionContext &context, const OptionMap const bool isVector = options["vector"].as(); const std::string preciceConfig = options["precice-config"].as(); + constexpr int conventionalReadData = -1; + // potentially make use of a just-in-time mapping using a prescribed batch size + const int batchSize = options.count("read-just-in-time") + ? options["read-just-in-time"].as() + : conventionalReadData; + if (batchSize < -1) { + ASTE_ERROR << "Batch size needs to be greater or equal to zero."; + } + addLogIdentity(participantName, context.rank); ASTE_INFO << "ASTE Running in mapping test mode"; @@ -186,7 +195,11 @@ void aste::runMapperMode(const aste::ExecutionContext &context, const OptionMap asteConfiguration.participantName = "B"; asteConfiguration.preciceConfigFilename = preciceConfig; aste::asteInterface asteInterface; - asteInterface.meshName = "B-Mesh"; + if (batchSize == conventionalReadData) { + asteInterface.meshName = "B-Mesh"; + } else { + asteInterface.meshName = "A-Mesh"; + } asteInterface.meshFilePrefix = meshname; asteInterface.meshes = aste::BaseName(meshname).findAll(context); @@ -212,7 +225,14 @@ void aste::runMapperMode(const aste::ExecutionContext &context, const OptionMap asteInterface.meshes.front().loadMesh(asteInterface.mesh, dim, requireConnectivity); asteInterface.meshes.front().loadData(asteInterface.mesh); ASTE_INFO << "The loaded mesh " << asteInterface.meshes.front().filename() << " contains: " << asteInterface.mesh.summary(); - auto vertexIDs = aste::setupMesh(preciceInterface, asteInterface.mesh, asteInterface.meshName); + + std::vector vertexIDs; + std::vector coordinates; + if (batchSize == conventionalReadData) { + vertexIDs = aste::setupMesh(preciceInterface, asteInterface.mesh, asteInterface.meshName); + } else { + coordinates = aste::setupDirectMeshAccess(preciceInterface, asteInterface.mesh, asteInterface.meshName, context); + } ASTE_DEBUG << "Mesh setup completed on Rank " << context.rank; if (preciceInterface.requiresInitialData()) { @@ -263,8 +283,33 @@ void aste::runMapperMode(const aste::ExecutionContext &context, const OptionMap for (auto &asteInterface : asteConfiguration.asteInterfaces) { for (auto &meshdata : asteInterface.mesh.meshdata) { if (meshdata.type == aste::datatype::READ) { - meshdata.dataVector.resize(vertexIDs.size() * meshdata.numcomp); - preciceInterface.readData(asteInterface.meshName, "Data", vertexIDs, dt, meshdata.dataVector); + if (batchSize == conventionalReadData) { + meshdata.dataVector.resize(vertexIDs.size() * meshdata.numcomp); + preciceInterface.readData(asteInterface.meshName, "Data", vertexIDs, dt, meshdata.dataVector); + } else { + int dim = preciceInterface.getMeshDimensions(asteInterface.meshName); + int nVertices = coordinates.size() / dim; + meshdata.dataVector.resize(nVertices * meshdata.numcomp); + + // pass everything in one go + if (batchSize == 0) { + preciceInterface.mapAndReadData(asteInterface.meshName, "Data", coordinates, dt, meshdata.dataVector); + } else { // pass in batches + for (int i = 0; i < nVertices; i += batchSize) { + int availableVertices = std::min(i + batchSize, nVertices); + int coordStart = i * dim; + int coordEnd = availableVertices * dim; + int dataStart = i * meshdata.numcomp; + int dataEnd = availableVertices * meshdata.numcomp; + + ::precice::span coordinateBatch(coordinates.data() + coordStart, coordEnd - coordStart); + ::precice::span dataBatch(meshdata.dataVector.data() + dataStart, dataEnd - dataStart); + + // Call the API function with the current batch + preciceInterface.mapAndReadData(asteInterface.meshName, "Data", coordinateBatch, dt, dataBatch); + } + } + } ASTE_DEBUG << "Data read: " << asteInterface.mesh.previewData(meshdata); } } diff --git a/src/utilities.cpp b/src/utilities.cpp index 9e1d65cc..a10306d9 100644 --- a/src/utilities.cpp +++ b/src/utilities.cpp @@ -72,6 +72,51 @@ void aste::setupEdgeIDs(precice::Participant &interface, const aste::Mesh &mesh, } } +std::vector aste::setupDirectMeshAccess(precice::Participant &interface, const aste::Mesh &mesh, const std::string &meshName, const aste::ExecutionContext &exec) +{ + ASTE_DEBUG << "Setting up direct access for: " << meshName; + ASTE_DEBUG << "Setting up coordinates vector for mesh..."; + + const auto dim = interface.getMeshDimensions(meshName); + const auto nvertices = mesh.positions.size(); + std::vector coordinates(dim * nvertices); + for (unsigned long i = 0; i < nvertices; ++i) { + const auto &pos = mesh.positions[i]; + assert(pos.size() == static_cast(dim)); + std::copy(pos.begin(), pos.end(), &coordinates[i * dim]); + } + + ASTE_DEBUG << "Computing the (rank-local) bounding box:"; + + std::vector bb(2 * dim); + + // for parallel runs, we define a proper bounding box + if (exec.isParallel()) { + // Initialize min and max values + for (int i = 0; i < dim; ++i) { + bb[2 * i] = std::numeric_limits::max(); // min values + bb[2 * i + 1] = std::numeric_limits::lowest(); // max values + } + + // Iterate through the coordinates + for (size_t i = 0; i < coordinates.size(); i += dim) { + for (int j = 0; j < dim; ++j) { + double coord = coordinates[i + j]; + bb[2 * j] = std::min(coord - 1e-12, bb[2 * j]); // Update min + bb[2 * j + 1] = std::max(coord + 1e-12, bb[2 * j + 1]); // Update max + } + } + } else { + // for serial runs, we just take everything (to account for potential discretization imbalances) + for (int i = 0; i < dim; ++i) { + bb[2 * i] = std::numeric_limits::lowest(); // min values + bb[2 * i + 1] = std::numeric_limits::max(); // max values + } + } + interface.setMeshAccessRegion(meshName, bb); + return coordinates; +} + std::vector aste::setupMesh(precice::Participant &interface, const aste::Mesh &mesh, const std::string &meshName) { auto tstart = std::chrono::steady_clock::now(); diff --git a/src/utilities.hpp b/src/utilities.hpp index f2578f55..23389b2d 100644 --- a/src/utilities.hpp +++ b/src/utilities.hpp @@ -60,4 +60,6 @@ void setupEdgeIDs(precice::Participant &interface, const aste::Mesh &mesh, const * @return std::vector a vector of vertexIDs in preCICE */ std::vector setupMesh(precice::Participant &interface, const aste::Mesh &mesh, const std::string &meshName); + +std::vector setupDirectMeshAccess(precice::Participant &interface, const aste::Mesh &mesh, const std::string &meshName, const aste::ExecutionContext &exec); } // namespace aste diff --git a/tools/mapping-tester/config-template.xml b/tools/mapping-tester/config-template.xml index 4b64eb8a..d560c7e3 100644 --- a/tools/mapping-tester/config-template.xml +++ b/tools/mapping-tester/config-template.xml @@ -1,6 +1,6 @@ - + @@ -15,10 +15,12 @@ - + + {% if mapping.get('batch-size') == "-1" %} + {% endif %} @@ -41,13 +43,16 @@ + {% if mapping.get('batch-size') == "-1" %} - + {% endif %} + + {% if mapping.constraint == "consistent" %} - + - + {% if mapping.kind.startswith("rbf") %} {% if mapping.executor %} diff --git a/tools/mapping-tester/generate.py b/tools/mapping-tester/generate.py index aeed34b8..eacc2c1a 100755 --- a/tools/mapping-tester/generate.py +++ b/tools/mapping-tester/generate.py @@ -55,6 +55,7 @@ def generateCases(setup): "executoroptions": mapping.get( "executor-options", "" ), + "batch-size": mapping.get("batch-size", "-1"), }, "A": { "ranks": ranksA, @@ -158,9 +159,15 @@ def createRunScript(outdir: pathlib.Path, path: pathlib.Path, case): path, walk_up=True ) mapped_data_name = case["function"] + "(mapped)" + + # Handle batch_size condition + batch_size_flag = "" output = "--output mapped" if case["computeAccuracy"] else "" - bcmd = f'env {time_command} -f %M -a -o memory-B.log precice-aste-run -v -a -p B --data "{mapped_data_name}" --mesh {bmeshLocation} {output} || kill 0 &' + if case["mapping"]["batch-size"] != "-1": + batch_size_flag = "--read-just-in-time {}".format(case["mapping"]["batch-size"]) + # Generate runner script for participant B + bcmd = f'env {time_command} -f %M -a -o memory-B.log precice-aste-run -v -a -p B --data "{mapped_data_name}" --mesh {bmeshLocation} {output} {batch_size_flag} || kill 0 &' if branks > 1: bcmd = "mpirun -n {} $ASTE_B_MPIARGS {}".format(branks, bcmd)