5from ..Project
import Project
11 Optional realisation of exahype2.api.Project.mainfile_generator
13 It constructs a main function that times the performance of different functions
14 and assesses their accuracy.
18 def __init__(self, project: Project, function_calls: str, additional_includes: str):
25 "Create main file for {}".format(self.
project._project_name)
33 Adapter that speaks the construct_output(output, main_name) protocol
34 peano4.api.Project.generate() expects from its main attribute (see
35 peano4.api.runner.DefaultSequence, whose job this class takes over for
36 an ExaHyPE 2 project), but fills the header/implementation pair with
37 ExaHyPE-flavoured content instead of the mesh-agnostic default.
39 project is the peano4-level project (peano4.api.Project) this sequence
40 is attached to as .main - it is where SUBDIRECTORIES/SUBNAMESPACE/
41 rootnamespace come from, mirroring peano4.api.runner.DefaultSequence.
43 exahype_project is the exahype2.api.Project this main file is generated
44 for - it is where the runtime settings (TIMEOUT, NUMBER_OF_THREADS,
45 GPUS, MULTICORE_ORCHESTRATION) come from, as those live on the ExaHyPE
46 2 project rather than on the plain peano4 one.
51// This file is part of the ExaHyPE2 project. For conditions of distribution and
52// use, please see the copyright notice at www.peano-framework.org
55int main(int argc, char** argv);
59// This file is part of the ExaHyPE2 project. For conditions of distribution and
60// use, please see the copyright notice at www.peano-framework.org
61#include "{{CLASSNAME}}.h"
63#include "peano4/peano.h"
64#include "exahype2/Simulation.h"
65#include "exahype2/CellData.h"
66#include "tarch/NonCriticalAssertions.h"
67#include "tarch/accelerator/accelerator.h"
68#include "tarch/accelerator/Device.h"
69#include "tarch/multicore/multicore.h"
70#include "tarch/multicore/Core.h"
71#include "tarch/multicore/BooleanSemaphore.h"
72#include "tarch/multicore/Lock.h"
73#include "tarch/logging/Log.h"
74#include "tarch/logging/LogFilterFileReader.h"
75#include "tarch/timing/Measurement.h"
76#include "tarch/timing/Watch.h"
77{{ADDITIONAL_INCLUDES}}
79{% if SUBDIRECTORIES -%}
80{% for subdirectory in SUBDIRECTORIES %}
81#include "{{subdirectory}}Constants.h"
82#include "{{subdirectory}}repositories/DataRepository.h"
83#include "{{subdirectory}}repositories/StepRepository.h"
84#include "{{subdirectory}}repositories/SolverRepository.h"
85#include "{{subdirectory}}runners/SetupSimulation.h"
86#include "{{subdirectory}}runners/TimeStep.h"
87#include "{{subdirectory}}runners/Plot.h"
88#include "{{subdirectory}}runners/MeshAdaptation.h"
92#include "repositories/DataRepository.h"
93#include "repositories/StepRepository.h"
94#include "repositories/SolverRepository.h"
95#include "runners/SetupSimulation.h"
96#include "runners/TimeStep.h"
97#include "runners/Plot.h"
98#include "runners/MeshAdaptation.h"
101using namespace {{NAMESPACE | join("::")}};
102tarch::logging::Log _log( "{{NAMESPACE | join("::")}}" );
103::tarch::timing::Measurement timingComputeKernel;
104::tarch::multicore::BooleanSemaphore validateOutcomeSemaphore;
106double validMaxEigenvalue = 0.0;
107double* validOutcome = nullptr;
109constexpr double TimeStamp = 0.5;
110constexpr double TimeStepSize = 1e-6;
111constexpr double CellSize = 0.1;
112constexpr double CellOffset = 4.0;
114constexpr int NumberOfInputEntriesPerPatch
115 = (repositories::{{SOLVER_INSTANCE}}.PatchSize + 2 * repositories::{{SOLVER_INSTANCE}}.HaloSize)
116 * (repositories::{{SOLVER_INSTANCE}}.PatchSize + 2 * repositories::{{SOLVER_INSTANCE}}.HaloSize)
118 * (repositories::{{SOLVER_INSTANCE}}.PatchSize + 2 * repositories::{{SOLVER_INSTANCE}}.HaloSize)
120 * (repositories::{{SOLVER_INSTANCE}}.NumberOfUnknowns + repositories::{{SOLVER_INSTANCE}}.NumberOfAuxiliaryVariables);
122constexpr int NumberOfOutputEntriesPerPatch
123 = (repositories::{{SOLVER_INSTANCE}}.PatchSize + 0)
124 * (repositories::{{SOLVER_INSTANCE}}.PatchSize + 0)
126 * (repositories::{{SOLVER_INSTANCE}}.PatchSize + 0)
128 * (repositories::{{SOLVER_INSTANCE}}.NumberOfUnknowns + repositories::{{SOLVER_INSTANCE}}.NumberOfAuxiliaryVariables);
130constexpr int NumberOfFiniteVolumesPerPatch
131 = repositories::{{SOLVER_INSTANCE}}.PatchSize
132 * repositories::{{SOLVER_INSTANCE}}.PatchSize
134 * repositories::{{SOLVER_INSTANCE}}.PatchSize
139 * Set input data according to the initial conditions provided for the solver
141void initInputData(double* Q) {
142 parallelDfor( volume, repositories::{{SOLVER_INSTANCE}}.PatchSize + 2 * repositories::{{SOLVER_INSTANCE}}.HaloSize) {
143 int index = peano4::utils::dLinearised(volume,repositories::{{SOLVER_INSTANCE}}.PatchSize + 2 * repositories::{{SOLVER_INSTANCE}}.HaloSize) * (repositories::{{SOLVER_INSTANCE}}.NumberOfUnknowns + repositories::{{SOLVER_INSTANCE}}.NumberOfAuxiliaryVariables);
144 repositories::{{SOLVER_INSTANCE}}.initPointWisely(
145 ::exahype2::fv::getVolumeCentre( CellOffset + 0.5 * CellSize, CellSize, repositories::{{SOLVER_INSTANCE}}.PatchSize, volume),
152 * Store outcome of one compute kernel
154 * Make a persistent snapshot of a solution and assume, from hereon, that
155 * this snapshot is the valid data. You can call this routine as often as
156 * you want. Only the very first call will trigger a snapshot.
158void storeOutcome(const double* Q, const double maxEigenvalue) {
159 ::tarch::multicore::Lock lock(validateOutcomeSemaphore);
160 if (validOutcome == nullptr) {
161 validOutcome = new double[NumberOfOutputEntriesPerPatch]{0.0};
162 std::memcpy(validOutcome, Q, sizeof(double) * NumberOfOutputEntriesPerPatch);
163 validMaxEigenvalue = maxEigenvalue;
164 logInfo("storeOutcome()", "Bookmarked reference solution");
170 * Validate data against pre-stored simulation outcome
172 * Works if and only if storeOutcome has been performed before.
174 * @return Tuple of maximum difference and total number of errors.
176std::tuple<double, int> validateOutcome(
179 const double maxEigenvalue
181 ::tarch::multicore::Lock lock(validateOutcomeSemaphore);
184 double maxDifference = 0.0;
186 for (int i = 0; i < NumberOfOutputEntriesPerPatch; i++) {
187 if (not ::tarch::la::equals(Q[i], validOutcome[i], Accuracy)) {
189 std::cerr.precision(16);
193 << "Q[" << i << "]!=validOutcome[" << i << "]: " << Q[i]
194 << "!=" << validOutcome[i]
197 maxDifference = std::max(maxDifference, std::abs(Q[i] - validOutcome[i]));
201 if (not tarch::la::equals(maxEigenvalue, validMaxEigenvalue, Accuracy)) {
202 std::cerr.precision(16);
206 << " maxEigenvalue[" << patchIndex << "]!=validMaxEigenvalue["
207 << patchIndex << "]: " << maxEigenvalue << "!=" << validMaxEigenvalue
212 return {maxDifference, errors};
217 const std::string& kernelIdentificator,
218 const ::tarch::timing::Measurement& timingKernelLaunch,
221 std::stringstream ss;
223 ss << kernelIdentificator << ":\\n\\t";
225 ss << timingComputeKernel.getValue() << " |\\n\\t";
227 << (timingComputeKernel.getValue() / patches / NumberOfFiniteVolumesPerPatch
230 ss << timingComputeKernel.toString() << " |\\n\\t";
232 ss << timingKernelLaunch.getValue() << " |\\n\\t";
234 << (timingKernelLaunch.getValue() / patches / NumberOfFiniteVolumesPerPatch
236 ss << " |\\n\\t" << timingKernelLaunch.toString();
238 logInfo("reportRuntime()", ss.str());
243 * We want to use all kernels exactly the same way. However, the various
244 * kernels all have slightly different signatures. So we write small helper
245 * functions (wrappers) which map the generic test signature onto the
248 * To make this possible, all parameters which are not part of the generic
249 * interface, i.e., which are not patch data or the boolean, have to be
250 * mapped onto template arguments.
252template <void (*Kernel)(
253 exahype2::CellData<double, double>&,
254 const ::exahype2::fv::rusanov::FluxFunctor&,
255 const ::exahype2::fv::rusanov::MaxEigenvalueFunctor&,
256 tarch::timing::Measurement&
258void wrapHostKernel(int device, exahype2::CellData<double, double>& patchData) {
259 assertionEquals(device, tarch::accelerator::Device::HostDevice);
263 const double* __restrict__ Q,
264 const tarch::la::Vector<Dimensions, double>& x,
266 double* __restrict__ F
268 repositories::instanceOfEulerFVSmooth.flux(Q, x, normal, F);
271 const double* __restrict__ Q,
272 const tarch::la::Vector<Dimensions, double>& x,
274 double* __restrict__ lambdaMax
276 repositories::instanceOfEulerFVSmooth.maxEigenvalue(Q, x, normal, lambdaMax);
282template <void (*Kernel)(exahype2::CellData<double, double>&, tarch::timing::Measurement&)>
283void wrapHostKernel(int device, exahype2::CellData<double, double>& patchData) {
284 assertionEquals(device, tarch::accelerator::Device::HostDevice);
285 Kernel(patchData, timingComputeKernel);
288 template <void (*Kernel)(exahype2::CellData<double, double>&, tarch::timing::Measurement&, peano4::utils::LoopPlacement),
289 peano4::utils::LoopPlacement loopPlacement>
290void wrapHostKernel(int device, exahype2::CellData<double, double>& patchData) {
291 assertionEquals(device, tarch::accelerator::Device::HostDevice);
292 Kernel(patchData, timingComputeKernel, loopPlacement);
295template <void (*Kernel)(int, exahype2::CopyCellDataGPU<double, double>&, tarch::timing::Measurement&)>
296void wrapDeviceKernel(int device, ::exahype2::CellData<double, double>& patchData) {
297 assertion(device != tarch::accelerator::Device::HostDevice);
298 ::exahype2::CopyCellDataGPU<double, double> gpuCellData(patchData, NumberOfInputEntriesPerPatch, NumberOfOutputEntriesPerPatch, device);
299 Kernel(device, gpuCellData, timingComputeKernel);
300 gpuCellData.copyBackAndDestroy();
304 * Run the benchmark for one particular number of patches
306 * This operation benchmarks exclusively the host performance. This happens in
307 * two steps: We first assess the baseline performance, i.e., the serial kernel
308 * call, and then we look if the multithreaded stateless optimisation pays off
309 * and what speedup we obtain.
311 * These two steps may run on multiple threads to mimic the spacetree tasking
312 * and domain decomposition. In OpenMP for example, there is
313 * one parallel loop around the two steps which invokes the same benchmark per
314 * loop entry. However, only the first thread always will report its runtime.
315 * If you are not bandwidth-bound, the data you obtain should thus be
316 * independent of the number of threads used.
318 * @param numberOfPatches Number of patches to study
320void runBenchmarks(int numberOfPatches, int launchingThreads) {
321 ::peano4::grid::GridTraversalEvent event;
322 event.setX(CellOffset);
323 event.setH(CellSize);
324 ::peano4::datamanagement::CellMarker marker(event);
328 std::function<void(int device, ::exahype2::CellData<double, double>& patchData)> kernel,
329 const std::string& markerName,
330 int launchingThreads,
334 timingComputeKernel.erase();
335 ::tarch::timing::Measurement timingKernelLaunch;
337 // TODO: Does the number of samples change the outcome of the solution?
338 for (int j = 0; j < NumberOfSamples; j++) {
339 //parallelFor(launchingThread, launchingThreads) {
340 //for (int launchingThread=0; launchingThread < launchingThreads; launchingThread++){
341 ::exahype2::CellData<double, double> patchData(patches);
342 for (int i = 0; i < patches; i++) {
343 patchData.QIn[i] = ::tarch::allocateMemory<double>(
344 NumberOfInputEntriesPerPatch,
345 ::tarch::MemoryLocation::ManagedSharedAcceleratorDeviceMemory
347 patchData.t[i] = TimeStamp;
348 patchData.dt[i] = TimeStepSize;
349 patchData.QOut[i] = ::tarch::allocateMemory<double>(
350 NumberOfOutputEntriesPerPatch,
351 ::tarch::MemoryLocation::ManagedSharedAcceleratorDeviceMemory
353 patchData.cellCentre[i] = ::tarch::la::Vector<Dimensions, double>(
354 CellOffset + 0.5 * CellSize
356 patchData.cellSize[i] = ::tarch::la::Vector<Dimensions, double>(
359 patchData.maxEigenvalue[i] = 0.0;
360 initInputData(patchData.QIn[i]);
364 NumberOfOutputEntriesPerPatch * sizeof(double)
368 ::tarch::timing::Watch
369 watchKernelLaunch("::runBenchmarks", "assessKernel(...)", false);
370 kernel(device, patchData);
371 watchKernelLaunch.stop();
372 timingKernelLaunch.setValue(watchKernelLaunch.getCalendarTime());
374 if constexpr (Accuracy > 0.0) {
376 double maxDifference = 0.0;
377 for (int i = 0; i < patches; i++) {
378 storeOutcome(patchData.QOut[i], patchData.maxEigenvalue[i]);
379 auto [maxDifferencePerPatch, errorsPerPatch] = validateOutcome(
382 patchData.maxEigenvalue[i]
384 errors += errorsPerPatch;
385 maxDifference = std::max(maxDifference, maxDifferencePerPatch);
391 "Max difference of outcome from \\""
392 << markerName << "\\" for all patches is " << maxDifference
393 << " (admissible accuracy=" << Accuracy << ")"
394 << " for " << errors << " entries"
400 for (int i = 0; i < patches; i++) {
403 ::tarch::MemoryLocation::ManagedSharedAcceleratorDeviceMemory
407 ::tarch::MemoryLocation::ManagedSharedAcceleratorDeviceMemory
414 if (::tarch::mpi::Rank::getInstance().getRank() == ::tarch::mpi::Rank::getGlobalMasterRank()) {
415 reportRuntime(markerName, timingKernelLaunch, patches);
420 const int device = 0;
421 const int rank = ::tarch::mpi::Rank::getInstance().getRank();
422 const int numberOfRanks = ::tarch::mpi::Rank::getInstance().getNumberOfRanks(
424 const int patchesPerProcess = numberOfPatches / numberOfRanks;
425 const int remainder = numberOfPatches % numberOfRanks;
426 const int startPatch = rank * patchesPerProcess;
427 const int endPatch = startPatch + patchesPerProcess
428 + (rank == numberOfRanks - 1 ? remainder : 0);
429 const int localPatches = endPatch - startPatch;
432 std::stringstream ss;
435 ss << "Kernel ID:\\n\\t";
436 ss << "Compute Kernel Time |\\n\\t";
437 ss << "Compute Kernel Time (Normalised) |\\n\\t";
438 ss << "Compute Kernel String |\\n\\t";
439 ss << "Kernel Launch Time |\\n\\t";
440 ss << "Kernel Launch Time (Normalised) |\\n\\t";
441 ss << "Kernel Launch String";
443 if (::tarch::mpi::Rank::getInstance().getRank() == ::tarch::mpi::Rank::getGlobalMasterRank()) {
444 logInfo("runBenchmarks()", "Number of patches per rank: " << localPatches);
445 logInfo("runBenchmarks()", ss.str());
448 std::string deviceString;
450 std::vector<int> devices(numberOfRanks);
451 MPI_Gather(&device, 1, MPI_INT, devices.data(), 1, MPI_INT, 0, MPI_COMM_WORLD);
452 for (int i = 0; i < numberOfRanks; i++) {
453 deviceString += std::to_string(devices[i]);
454 if (i < numberOfRanks - 1) {
459 deviceString = std::to_string(device);
465int main(int argc, char** argv) {
466 constexpr int ExitCodeSuccess = 0;
467 constexpr int ExitCodeInconsistentResults = 1;
468 constexpr int ExitCodeInvalidArguments = 2;
469 constexpr int ExitCodeInvalidBuild = 3;
471 peano4::initParallelEnvironment(&argc, &argv);
473 // Do this early, so people can use logInfo/logWarning/... properly from
475 {% if LOG_FILTER_FILE!="" %}
476 if ( not tarch::logging::LogFilterFileReader::parsePlainTextFile( "{{LOG_FILTER_FILE}}" ) ) {
477 if (tarch::mpi::Rank::getInstance().isGlobalMaster()) {
478 logWarning( "main()", "filter file {{LOG_FILTER_FILE}} not found or file has been corrupted. Use default logging configuration" );
483 peano4::fillLookupTables();
484 {{NAMESPACE | join("::")}}::{{SUBNAMESPACE}}repositories::DataRepository::initDatatypes();
485 peano4::initSingletons(
486 {{NAMESPACE | join("::")}}::DomainOffset,
487 {{NAMESPACE | join("::")}}::DomainSize,
488 {{NAMESPACE | join("::")}}::PeriodicBC
491 if ( not ::exahype2::configureRuntime(
493 {{NUMBER_OF_THREADS}},
494 { {{GPUS|join(",")}} },
495 {{MULTICORE_ORCHESTRATION}}
497 return ExitCodeInvalidBuild;
500 {% if FENV_ARGS is defined and FENV_ARGS != "" -%}
501 feenableexcept({{FENV_ARGS}} );
505 for (int n = 0; n < NumberOfPatchesToStudy.size(); n++) {
506 runBenchmarks(NumberOfPatchesToStudy[n], NumberOfLaunchingThreads);
509 ::exahype2::shutdownRuntime(
510 {{NAMESPACE | join("::")}}::{{SUBNAMESPACE}}repositories::DataRepository::shutdownDatatypes
513 return ExitCodeSuccess;
517 def __init__(self, project, exahype_project, function_calls, additional_includes):
525 "SUBDIRECTORIES": list(self.
project.subdirectories),
526 "SUBNAMESPACE": self.
project._subdirectory +
"::" if self.
project._subdirectory
else "",
529 "MULTICORE_ORCHESTRATION": self.
exahype_project._multicore_orchestration,
533 "SOLVER_INSTANCE": self.
exahype_project._solvers[0].get_name_of_global_instance(),
541 classname = main_name,
542 namespace = self.
project.rootnamespace,
545 default_overwrite =
True,
548 output.makefile.add_h_file(main_name +
".h", generated=
True)
549 output.makefile.add_cpp_file(main_name +
".cpp", generated=
True)
550 output.add(generated_files)
Base class for the code generation steps that turn an ExaHyPE 2 Project's configuration into actual g...
Optional realisation of exahype2.api.Project.mainfile_generator.
__init__(self, Project project, str function_calls, str additional_includes)
construct_output(self, peano_project)
Add this generator's output to peano_project (a peano4.api.Project-compatible object,...
Adapter that speaks the construct_output(output, main_name) protocol peano4.api.Project....
construct_output(self, output, main_name)
__init__(self, project, exahype_project, function_calls, additional_includes)
Singleton logger for the Peano 4 Python API.