MAIA bb96820c
Multiphysics at AIA
Loading...
Searching...
No Matches
dgcartesiansolver.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The m-AIA AUTHORS
2//
3// This file is part of m-AIA (https://git.rwth-aachen.de/aia/m-AIA/m-AIA)
4//
5// SPDX-License-Identifier: LGPL-3.0-only
6
7#include "dgcartesiansolver.h"
8
9#include <algorithm>
10#include <array>
11#include <chrono>
12#include <cmath>
13#include <cstring>
14#include <functional>
15#include <iomanip>
16#include <iostream>
17#include <iterator>
18#include <numeric>
19#include "COMM/mpioverride.h"
21#include "IO/logtable.h"
22#include "IO/parallelio.h"
23#include "UTIL/hilbert.h"
24#include "UTIL/maiamath.h"
25#include "dgcartesianmortar.h"
26#include "globals.h"
27#include "sbpcartesianmortar.h"
28#include "typetraits.h"
29
30// Enable/disable compiler-specific diagnostics
31#if defined(MAIA_GCC_COMPILER)
32#pragma GCC diagnostic push
33// #pragma GCC diagnostic error "-Wold-style-cast"
34#endif
35
36using namespace std;
37using namespace maia; // Omit once this is all done within 'maia' namespace
38
50template <MInt nDim, class SysEqn>
52 Geometry<nDim>& geometry_, const MPI_Comm comm)
53 : maia::CartesianSolver<nDim, DgCartesianSolver<nDim, SysEqn>>(solverId_, gridProxy_, comm, true),
54 m_geometry(geometry_),
55 m_sponge(solverId_, comm),
56 m_sysEqn(solverId_) {
57 TRACE();
58
59 // Store currently used memory
60 const MLong previouslyAllocated = allocatedBytes();
61
62 // Initialize all solver-wide timers
63 initTimers();
64
65 // Initialize basic properties
67
68 // Input/output
70
71 // Numerical method
73
74 // Allocate general DG solver memory
76
77 // Initialize boundary condition factory
79
80 if(gridProxy_.isActive()) {
81 // Print information on used memory
82 printAllocatedMemory(previouslyAllocated, "DgCartesianSolver (solverId = " + to_string(m_solverId) + ")",
83 mpiComm());
84 }
85
86 RECORD_TIMER_STOP(m_timers[Timers::Constructor]);
87}
88
89
97template <MInt nDim, class SysEqn>
99 TRACE();
100 RECORD_TIMER_START(m_timers[Timers::Destructor]);
101 RECORD_TIMER_STOP(m_timers[Timers::Destructor]);
102 RECORD_TIMER_STOP(m_timers[Timers::SolverType]);
103}
104
105
112template <MInt nDim, class SysEqn>
114 TRACE();
115
116 // Invalidate timer ids
117 std::fill(m_timers.begin(), m_timers.end(), -1);
118
119 // Create timer group & timer for solver, and start the timer
120 NEW_TIMER_GROUP_NOCREATE(m_timerGroup, "DgCartesianSolver (solverId = " + to_string(m_solverId) + ")");
121 NEW_TIMER_NOCREATE(m_timers[Timers::SolverType], "total object lifetime", m_timerGroup);
122 RECORD_TIMER_START(m_timers[Timers::SolverType]);
123
124 // Create & start constructor timer
125 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::Constructor], "Constructor", m_timers[Timers::SolverType]);
126 RECORD_TIMER_START(m_timers[Timers::Constructor]);
127
128 // Create regular solver-wide timers
129 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::Run], "run", m_timers[Timers::SolverType]);
130 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::RunInit], "run initialization", m_timers[Timers::Run]);
131 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::InitSolverObjects], "initialize solver infrastructure",
132 m_timers[Timers::RunInit]);
133 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::InitMortarProjection], "initMortarProjection",
134 m_timers[Timers::InitSolverObjects]);
135 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::InitData], "initialize solver data", m_timers[Timers::RunInit]);
136 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::InitialCondition], "initialCondition", m_timers[Timers::InitData]);
137 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::InitMainLoop], "initialize main loop", m_timers[Timers::RunInit]);
138 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::MainLoop], "main loop", m_timers[Timers::Run]);
139 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::CalcTimeStep], "calcTimeStep", m_timers[Timers::MainLoop]);
140 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::ResetExternalSources], "resetExternalSources", m_timers[Timers::MainLoop]);
141 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::RungeKuttaStep], "timeStepRk", m_timers[Timers::MainLoop]);
142 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::TimeDeriv], "calcDgTimeDerivative", m_timers[Timers::RungeKuttaStep]);
143 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::ResetRHS], "resetRHS", m_timers[Timers::TimeDeriv]);
144 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::Prolong], "prolong to surfaces", m_timers[Timers::TimeDeriv]);
145 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::ForwardProjection], "forward projection", m_timers[Timers::TimeDeriv]);
146 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::SurfExchange], "MPI surface exchange", m_timers[Timers::TimeDeriv]);
147 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::SurfExchangeComm], "communication", m_timers[Timers::SurfExchange]);
148 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::SECommSend], "send", m_timers[Timers::SurfExchangeComm]);
149 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::SECommRecv], "recv", m_timers[Timers::SurfExchangeComm]);
150 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::SurfExchangeCopy], "copy operations", m_timers[Timers::SurfExchange]);
151 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::SECopySend], "send", m_timers[Timers::SurfExchangeCopy]);
152 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::SECopyRecv], "recv", m_timers[Timers::SurfExchangeCopy]);
153 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::SurfExchangeWait], "waiting", m_timers[Timers::SurfExchange]);
154 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::SEWaitSend], "send", m_timers[Timers::SurfExchangeWait]);
155 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::SEWaitRecv], "recv", m_timers[Timers::SurfExchangeWait]);
156 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::VolInt], "calcVolumeIntegral", m_timers[Timers::TimeDeriv]);
157 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::Flux], "flux calculation", m_timers[Timers::TimeDeriv]);
158 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::FluxBndry], "calcBoundarySurfaceFlux", m_timers[Timers::Flux]);
159 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::FluxInner], "calcInnerSurfaceFlux", m_timers[Timers::Flux]);
160 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::FluxMPI], "calcMpiSurfaceFlux", m_timers[Timers::Flux]);
161 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::SurfInt], "surface integrals", m_timers[Timers::TimeDeriv]);
162 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::Jacobian], "applyJacobian", m_timers[Timers::TimeDeriv]);
163 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::Sources], "calcSourceTerms", m_timers[Timers::TimeDeriv]);
164 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::ExternalSources], "applyExternalSourceTerms", m_timers[Timers::TimeDeriv]);
165 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::Sponge], "calcSpongeTerms", m_timers[Timers::TimeDeriv]);
166 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::TimeInt], "time integration", m_timers[Timers::RungeKuttaStep]);
167 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::MainLoopIO], "I/O", m_timers[Timers::MainLoop]);
168 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::Analysis], "solution analysis", m_timers[Timers::MainLoop]);
169 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::CleanUp], "cleanUp", m_timers[Timers::Run]);
170 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::Destructor], "Destructor", m_timers[Timers::SolverType]);
171 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::AdaptiveRefinement], "adaptive refinement", m_timers[Timers::MainLoop]);
172
173 // Create accumulated timers that monitor selected subsystems
174 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::Accumulated], "selected accumulated timers", m_timers[Timers::SolverType]);
175 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::IO], "IO", m_timers[Timers::Accumulated]);
176 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::SaveSolutionFile], "saveSolutionFile", m_timers[Timers::IO]);
177 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::SaveRestartFile], "saveRestartFile", m_timers[Timers::IO]);
178 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::AnalyzeTimeStep], "analyzeTimeStep", m_timers[Timers::Accumulated]);
179 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::MPI], "MPI", m_timers[Timers::Accumulated]);
180 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::MPIComm], "communication", m_timers[Timers::MPI]);
181 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::MPICopy], "copy operations", m_timers[Timers::MPI]);
182 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::MPIWait], "waiting", m_timers[Timers::MPI]);
183
184 // Set status to initialized
185 m_isInitTimers = true;
186}
187
188
196template <MInt nDim, class SysEqn>
198 TRACE();
199
215 m_maxNoSurfaces = -1;
216 m_maxNoSurfaces = Context::getSolverProperty<MInt>("maxNoSurfaces", m_solverId, AT_, &m_maxNoSurfaces);
217
231 m_timeSteps = Context::getSolverProperty<MInt>("timeSteps", m_solverId, AT_);
232}
233
234
242template <MInt nDim, class SysEqn>
244 TRACE();
245
260 m_alwaysSaveFinalSolution = m_solutionInterval > 0;
261 m_alwaysSaveFinalSolution =
262 Context::getSolverProperty<MBool>("alwaysSaveFinalSolution", m_solverId, AT_, &m_alwaysSaveFinalSolution);
263
278 m_alwaysSaveFinalRestart = m_restartInterval > 0;
279 m_alwaysSaveFinalRestart =
280 Context::getSolverProperty<MBool>("alwaysSaveFinalRestart", m_solverId, AT_, &m_alwaysSaveFinalRestart);
281
296 m_saveNodeVariablesToSolutionFile = false;
297 m_saveNodeVariablesToSolutionFile = Context::getSolverProperty<MBool>("saveNodeVariablesToSolutionFile", m_solverId,
298 AT_, &m_saveNodeVariablesToSolutionFile);
299
314 m_analysisInterval = 0;
315 // m_analysisInterval
316 m_analysisInterval = Context::getSolverProperty<MInt>("analysisInterval", m_solverId, AT_, &m_analysisInterval);
317 if(m_analysisInterval < 0) {
318 TERMM(1, "Analysis interval must be >= 0 (is: " + to_string(m_analysisInterval) + ").");
319 }
320
321 // Initialize I/O properties of helper class for point data gathering & saving
322 m_pointData.setInputOutputProperties();
323 // ..., for surface data
324 m_surfaceData.setInputOutputProperties();
325 // ... and for volume data
326 m_volumeData.setInputOutputProperties();
327
328 // Initialize I/O properties of dg slices
329 m_slice.setProperties();
330
345 /* m_aliveInterval = m_analysisInterval / 10; */
346 /* m_aliveInterval = */
347 /* Context::getSolverProperty<MInt>("aliveInterval", m_solverId, AT_, &m_aliveInterval); */
348 /* if (m_aliveInterval < 0) { */
349 /* TERMM(1, "Alive interval must be >= 0 (is: " + to_string(m_analysisInterval) + ")."); */
350 /* } */
351
360 m_writeSpongeEta = false;
361 m_writeSpongeEta = Context::getSolverProperty<MBool>("writeSpongeEta", m_solverId, AT_, &m_writeSpongeEta);
362
372 m_writeTimeDerivative = 0;
373 m_writeTimeDerivative =
374 Context::getSolverProperty<MBool>("writeTimeDerivative", m_solverId, AT_, &m_writeTimeDerivative);
375
386 m_noMinutesEndAutoSave = 10;
387 m_noMinutesEndAutoSave =
388 Context::getSolverProperty<MInt>("noMinutesEndAutoSave", m_solverId, AT_, &m_noMinutesEndAutoSave);
389
398 m_endAutoSaveCheckInterval = 50;
399 m_endAutoSaveCheckInterval =
400 Context::getSolverProperty<MInt>("endAutoSaveCheckInterval", m_solverId, AT_, &m_endAutoSaveCheckInterval);
401
416 /* m_updateCellWeights = false; */
417 /* m_updateCellWeights = Context::getSolverProperty<MBool>("updateCellWeights", */
418 /* m_solverId, AT_, &m_updateCellWeights); */
419
432 m_weightDgRbcElements = false;
433 m_weightDgRbcElements =
434 Context::getSolverProperty<MBool>("weightDgRbcElements", m_solverId, AT_, &m_weightDgRbcElements);
435
436 /* if (m_updateCellWeights) { */
437 {
450 m_weightPerNode = 1.0;
451 m_weightPerNode = Context::getSolverProperty<MFloat>("weightPerNode", m_solverId, AT_, &m_weightPerNode);
452
465 m_weightPerElement = 0.0;
466 m_weightPerElement = Context::getSolverProperty<MFloat>("weightPerElement", m_solverId, AT_, &m_weightPerElement);
467
481 /* m_restoreDefaultWeights = false; */
482 /* m_restoreDefaultWeights = Context::getSolverProperty<MBool>("restoreDefaultWeights", */
483 /* m_solverId, AT_, &m_restoreDefaultWeights); */
484 }
485
498 m_writeInitialSolution = true;
499 m_writeInitialSolution =
500 Context::getSolverProperty<MBool>("writeInitialSolution", m_solverId, AT_, &m_writeInitialSolution);
501
514 m_writeNodeVarsFile = true;
515 m_writeNodeVarsFile = Context::getSolverProperty<MBool>("writeNodeVarsFile", m_solverId, AT_, &m_writeNodeVarsFile);
516}
517
518
525template <MInt nDim, class SysEqn>
527 TRACE();
528
541 m_startTime = Context::getSolverProperty<MFloat>("startTime", m_solverId, AT_);
542
555 m_finalTime = Context::getSolverProperty<MFloat>("finalTime", m_solverId, AT_);
556
557 if(m_startTime > m_finalTime || m_startTime < F0) {
558 cerr << "Illegal setup of the time integration parameters. Aborting..." << endl;
559 m_log << "Illegal setup of the time integration parameters. Aborting..." << endl;
560 mTerm(1, AT_, "Illegal setup of the time integration parameters.");
561 }
562
577 m_calcTimeStepInterval = 0;
578 m_calcTimeStepInterval =
579 Context::getSolverProperty<MInt>("calcTimeStepInterval", m_solverId, AT_, &m_calcTimeStepInterval);
580 if(m_calcTimeStepInterval < 0) {
581 TERMM(1, "Recalculation interval for time step must be >= 0 (is: " + to_string(m_calcTimeStepInterval) + ").");
582 }
583
592 m_sbpMode = false;
593 m_sbpMode = Context::getSolverProperty<MBool>("sbpMode", m_solverId, AT_, &m_sbpMode);
594
603 m_sbpOperator = "";
604 m_sbpOperator = Context::getSolverProperty<MString>("sbpOperator", m_solverId, AT_, &m_sbpOperator);
605
620 MString dgIntegrationMethod = m_sbpMode ? "DG_INTEGRATE_GAUSS_LOBATTO" : "DG_INTEGRATE_GAUSS";
621 m_dgIntegrationMethod =
622 string2enum(Context::getSolverProperty<MString>("dgIntegrationMethod", m_solverId, AT_, &dgIntegrationMethod));
623
641 MString dgTimeIntegrationScheme = "DG_TIMEINTEGRATION_CARPENTER_4_5";
642 m_dgTimeIntegrationScheme = string2enum(
643 Context::getSolverProperty<MString>("dgTimeIntegrationScheme", m_solverId, AT_, &dgTimeIntegrationScheme));
644
645 // Define the number of Rk-steps according to the scheme being used and it's coeffiecients
646 m_noRkStages = -1;
647 switch(m_dgTimeIntegrationScheme) {
649 default:
650 m_noRkStages = 5;
651
652 m_timeIntegrationCoefficientsA.resize(m_noRkStages);
653 m_timeIntegrationCoefficientsB.resize(m_noRkStages);
654 m_timeIntegrationCoefficientsC.resize(m_noRkStages);
655
656 m_timeIntegrationCoefficientsA = {{F0, 567301805773.0 / 1357537059087.0, 2404267990393.0 / 2016746695238.0,
657 3550918686646.0 / 2091501179385.0, 1275806237668.0 / 842570457699.0}};
658 m_timeIntegrationCoefficientsB = {{1432997174477.0 / 9575080441755.0, 5161836677717.0 / 13612068292357.0,
659 1720146321549.0 / 2090206949498.0, 3134564353537.0 / 4481467310338.0,
660 2277821191437.0 / 14882151754819.0}};
661 m_timeIntegrationCoefficientsC = {{F0, 1432997174477.0 / 9575080441755.0, 2526269341429.0 / 6820363962896.0,
662 2006345519317.0 / 3224310063776.0, 2802321613138.0 / 2924317926251.0}};
663 break;
664 }
665
667 m_noRkStages = 8;
668
669 m_timeIntegrationCoefficientsA.resize(m_noRkStages);
670 m_timeIntegrationCoefficientsB.resize(m_noRkStages);
671 m_timeIntegrationCoefficientsC.resize(m_noRkStages);
672
673 m_timeIntegrationCoefficientsA = {{F0, 0.7212962482279240, 0.0107733657161298, 0.5162584698930970,
674 1.7301002866322010, 5.2001293044030760, -0.7837058945416420,
675 0.5445836094332190}};
676 m_timeIntegrationCoefficientsB = {{0.2165936736758085, 0.1773950826411583, 0.0180253861162329, 0.0847347637254149,
677 0.8129106974622483, 1.9034160304227600, 0.1314841743399048,
678 0.2082583170674149}};
679 m_timeIntegrationCoefficientsC = {{F0, 0.2165936736758085, 0.2660343487538170, 0.2840056122522720,
680 0.3251266843788570, 0.4555149599187530, 0.7713219317101170,
681 0.9199028964538660}};
682 break;
683 }
684
686 m_noRkStages = 14;
687
688 m_timeIntegrationCoefficientsA.resize(m_noRkStages);
689 m_timeIntegrationCoefficientsB.resize(m_noRkStages);
690 m_timeIntegrationCoefficientsC.resize(m_noRkStages);
691
692 m_timeIntegrationCoefficientsA = {{F0, 0.718801210867241, 0.778533117342157, 0.005328279665404, 0.855297993402928,
693 3.95641382457746, 1.57805753805874, 2.08370945525741, 0.748333418276161,
694 0.703286110656336, -0.001391709611768, 0.093207536963746, 0.951420047087595,
695 7.11515716939226}};
696 m_timeIntegrationCoefficientsB = {{0.036776245431967, 0.313629660755396, 0.153184869186903, 0.003009708681818,
697 0.332629379064611, 0.244025140535086, 0.371887923959228, 0.620412622158244,
698 0.152404317302874, 0.076089492741927, 0.007760421404098, 0.002464728475538,
699 0.078034834004939, 5.50597772702696}};
700 m_timeIntegrationCoefficientsC = {{F0, 0.036776245431967, 0.124968526272503, 0.244617770227770, 0.247614953107042,
701 0.296931112038247, 0.397814964580264, 0.527085458944033, 0.698126999417570,
702 0.819089083535213, 0.852705988709862, 0.860471181746282, 0.862706037696998,
703 0.873421312760098}};
704 break;
705 }
706
708 m_noRkStages = 13;
709
710 m_timeIntegrationCoefficientsA.resize(m_noRkStages);
711 m_timeIntegrationCoefficientsB.resize(m_noRkStages);
712 m_timeIntegrationCoefficientsC.resize(m_noRkStages);
713
714 m_timeIntegrationCoefficientsA = {{F0, 0.6160178650170565, 0.4449487060774118, 1.0952033345276178,
715 1.2256030785959187, 0.2740182222332805, 0.0411952089052647, 0.1797084899153560,
716 1.1771530652064288, 0.4078831463120878, 0.8295636426191777, 4.7895970584252288,
717 0.6606671432964504}};
718 m_timeIntegrationCoefficientsB = {{0.0271990297818803, 0.1772488819905108, 0.0378528418949694, 0.6086431830142991,
719 0.2154313974316100, 0.2066152563885843, 0.0415864076069797, 0.0219891884310925,
720 0.9893081222650993, 0.0063199019859826, 0.3749640721105318, 1.6080235151003195,
721 0.0961209123818189}};
722 m_timeIntegrationCoefficientsC = {{F0, 0.0271990297818803, 0.0952594339119365, 0.1266450286591127,
723 0.1825883045699772, 0.3737511439063931, 0.5301279418422206, 0.5704177433952291,
724 0.5885784947099155, 0.6160769826246714, 0.6223252334314046, 0.6897593128753419,
725 0.9126827615920843}};
726 break;
727 }
728
730 m_noRkStages = 7;
731
732 m_timeIntegrationCoefficientsA.resize(m_noRkStages);
733 m_timeIntegrationCoefficientsB.resize(m_noRkStages);
734 m_timeIntegrationCoefficientsC.resize(m_noRkStages);
735
736 m_timeIntegrationCoefficientsA = {{F0, 0.808316387498383, 1.503407858773331, 1.053064525050744, 1.463149119280508,
737 0.659288128108783, 1.667891931891068}};
738 m_timeIntegrationCoefficientsB = {{0.0119705267309784, 0.8886897793820711, 0.4578382089261419, 0.5790045253338471,
739 0.3160214638138484, 0.2483525368264122, 0.0677123095940884}};
740 m_timeIntegrationCoefficientsC = {{F0, 0.0119705267309784, 0.1823177940361990, 0.5082168062551849,
741 0.6532031220148590, 0.8534401385678250, 0.9980466084623790}};
742 break;
743 }
744
746 m_noRkStages = 8;
747
748 m_timeIntegrationCoefficientsA.resize(m_noRkStages);
749 m_timeIntegrationCoefficientsB.resize(m_noRkStages);
750 m_timeIntegrationCoefficientsC.resize(m_noRkStages);
751
752 m_timeIntegrationCoefficientsA = {{F0, 0.5534431294501569, -0.0106598757020349, 0.5515812888932000,
753 1.8857903775587410, 5.7012957427932640, -2.1139039656647930,
754 0.5339578826675280}};
755 m_timeIntegrationCoefficientsB = {{0.0803793688273695, 0.5388497458569843, 0.0197497440903196, 0.0991184129733997,
756 0.7466920411064123, 1.6795842456188940, 0.2433728067008188,
757 0.1422730459001373}};
758 m_timeIntegrationCoefficientsC = {{F0, 0.0803793688273695, 0.3210064250338430, 0.3408501826604660,
759 0.3850364824285470, 0.5040052477534100, 0.6578977561168540,
760 0.9484087623348481}};
761 break;
762 }
763 }
764
765 // Sanity check to ensure a valid time integration scheme has been selected
766 if(m_noRkStages == -1) {
767 TERMM(1, "Invalid property value for time integration scheme");
768 }
769
770 MString dgPolynomialType = "DG_POLY_LEGENDRE";
771 m_dgPolynomialType =
772 string2enum(Context::getSolverProperty<MString>("dgPolynomialType", m_solverId, AT_, &dgPolynomialType));
773
786 m_initPolyDeg = -1;
787 m_initPolyDeg = Context::getSolverProperty<MInt>("initPolyDeg", m_solverId, AT_, &m_initPolyDeg);
788
789 if(m_initPolyDeg < 0) {
790 mTerm(1, AT_, "Negative polynomial order set in properties.");
791 }
792
793
808 m_minPolyDeg = numeric_limits<MInt>::max();
809 m_minPolyDeg = Context::getSolverProperty<MInt>("minPolyDeg", m_solverId, AT_, &m_minPolyDeg);
810
826 m_maxPolyDeg = -1;
827 m_maxPolyDeg = Context::getSolverProperty<MInt>("maxPolyDeg", m_solverId, AT_, &m_maxPolyDeg);
828
829 // Make sure that all values for the polynomimal order are consistent
830 // (for Gauss-Lobatto, the minimum polynomial degree is 1)
831 if(m_dgIntegrationMethod == DG_INTEGRATE_GAUSS_LOBATTO) {
832 m_initPolyDeg = max(1, m_initPolyDeg);
833 m_maxPolyDeg = max(1, m_maxPolyDeg);
834 m_minPolyDeg = max(1, m_minPolyDeg);
835 } else {
836 m_initPolyDeg = max(0, m_initPolyDeg);
837 m_maxPolyDeg = max(0, m_maxPolyDeg);
838 m_minPolyDeg = max(0, m_minPolyDeg);
839 }
840
841 m_minPolyDeg = min(m_minPolyDeg, m_initPolyDeg);
842 m_maxPolyDeg = max(m_maxPolyDeg, m_initPolyDeg);
843
844 // Determine range in noNodes1D which is needed for SBP mode
845 if(m_sbpMode) {
846 m_initNoNodes1D = -1;
847 m_initNoNodes1D = Context::getSolverProperty<MInt>("initNoNodes", m_solverId, AT_, &m_initNoNodes1D);
848
849 m_minNoNodes1D = Context::getSolverProperty<MInt>("minNoNodes", m_solverId, AT_, &m_initNoNodes1D);
850
851 m_maxNoNodes1D = Context::getSolverProperty<MInt>("maxNoNodes", m_solverId, AT_, &m_initNoNodes1D);
852
853 // Make sure that all values for the number of nodes are consistent
854 m_initNoNodes1D = max(2, m_initNoNodes1D);
855 m_maxNoNodes1D = max(2, m_maxNoNodes1D);
856 m_minNoNodes1D = max(2, m_minNoNodes1D);
857
858 m_minNoNodes1D = min(m_minNoNodes1D, m_initNoNodes1D);
859 m_maxNoNodes1D = max(m_maxNoNodes1D, m_initNoNodes1D);
860
861 } else { // DG
862 m_initNoNodes1D = m_initPolyDeg + 1;
863 m_minNoNodes1D = m_minPolyDeg + 1;
864 m_maxNoNodes1D = m_maxPolyDeg + 1;
865 }
866
880 m_calcErrorNorms = 1;
881 m_calcErrorNorms = Context::getSolverProperty<MBool>("calcErrorNorms", m_solverId, AT_, &m_calcErrorNorms);
882
883 if(m_calcErrorNorms) {
897 m_noAnalysisNodes = 2 * (m_maxPolyDeg + 1);
898 m_noAnalysisNodes = Context::getSolverProperty<MInt>("noAnalysisNodes", m_solverId, AT_, &m_noAnalysisNodes);
899 if(m_noAnalysisNodes < 2 * m_maxPolyDeg) {
900 const MString warning =
901 "WARNING: Polynomial degree for error norms should be at least twice the maximum polynomial degree";
902 m_log << warning << std::endl;
903 cerr0 << warning << std::endl;
904 }
905 m_polyDegAnalysis = m_noAnalysisNodes - 1;
906
907 if(m_sbpMode) {
908 m_noAnalysisNodes = m_initNoNodes1D;
909 m_polyDegAnalysis = m_initPolyDeg;
910 }
911
924 m_noErrorDigits = 6;
925 m_noErrorDigits = Context::getSolverProperty<MInt>("noErrorDigits", m_solverId, AT_, &m_noErrorDigits);
926 }
935 m_useSponge = false;
936 m_useSponge = Context::getSolverProperty<MBool>("useSponge", m_solverId, AT_, &m_useSponge);
937
938 MInt count = 0;
939 MString nameCoords = "prefCoordinates_" + to_string(count);
940 MString namePolyDeg = "prefPolyDeg_" + to_string(count);
941 MString nameOperator = "prefOperator_" + to_string(count);
942 MString nameNoNodes1D = "prefNoNodes_" + to_string(count);
943
944 // Loop over all p-refinement patches that are defined in the properties
945 while(Context::propertyExists(nameCoords, m_solverId) && Context::propertyExists(namePolyDeg, m_solverId)) {
946 // Get polynomial degree and check whether it fullfills the conditions
947 const MInt polyDeg = Context::getSolverProperty<MInt>(namePolyDeg, m_solverId, AT_);
948
949 if(polyDeg < m_minPolyDeg || polyDeg > m_maxPolyDeg) {
950 TERMM(1, "p-refinement: Polynomial degree of patch " + to_string(count)
951 + " doesn't fit into range defined by minimum and maximum "
952 "polynomial degree");
953 }
954
955 // Get the coordinates and check whether they fullfill the conditions
956 // Check if enough coordinates are defined
957 if(Context::propertyLength(nameCoords, m_solverId) != 2 * nDim) {
958 TERMM(1, "p-refinement: wrong number of coordinates for patch " + to_string(count));
959 }
960
961 array<MFloat, 2 * nDim> coords;
962 // Read and store patch coordinates and patch polynomial degree
963 for(MInt i = 0; i < 2 * nDim; i++) {
964 coords[i] = Context::getSolverProperty<MFloat>(nameCoords, m_solverId, AT_, i);
965 }
966
967 MString prefOperator = "";
968 prefOperator = Context::getSolverProperty<MString>(nameOperator, m_solverId, AT_, &prefOperator);
969
970 MInt prefNoNodes1D = polyDeg + 1;
971 prefNoNodes1D = Context::getSolverProperty<MInt>(nameNoNodes1D, m_solverId, AT_, &prefNoNodes1D);
972
973 m_prefPatchesCoords.push_back(coords);
974 m_prefPatchesPolyDeg.push_back(polyDeg);
975 m_prefPatchesOperators.push_back(prefOperator);
976 m_prefPatchesNoNodes1D.push_back(prefNoNodes1D);
977
978 // Increase counter and generate new names
979 count++;
980
981 nameCoords = "prefCoordinates_" + to_string(count);
982 namePolyDeg = "prefPolyDeg_" + to_string(count);
983 nameOperator = "prefOperator_" + to_string(count);
984 nameNoNodes1D = "prefNoNodes_" + to_string(count);
985 }
986
997 m_pref = 1;
998 m_pref = Context::getSolverProperty<MInt>("pref", m_solverId, AT_, &m_pref);
999
1007 MString adaptiveMethod = "DG_ADAPTIVE_NONE";
1008 m_adaptivePref = string2enum(Context::getSolverProperty<MString>("adaptivePref", m_solverId, AT_, &adaptiveMethod));
1009
1010 if(hasAdaptivePref()) {
1011 if(g_multiSolverGrid) {
1012 TERMM(1, "Multi-solver does not work with adaptive p-refinement yet");
1013 }
1021 m_adaptiveInterval = Context::getSolverProperty<MInt>("adaptiveInterval", m_solverId, AT_, &m_adaptiveInterval);
1022
1030 m_adaptiveThreshold =
1031 Context::getSolverProperty<MFloat>("adaptiveThreshold", m_solverId, AT_, &m_adaptiveThreshold);
1032 if(m_adaptiveInterval < 0 || m_adaptiveThreshold < 0) {
1033 stringstream errorMessage;
1034 errorMessage << "Error: Adaptive setting variables cannot be set to < 0 "
1035 "(adaptiveInterval/adaptiveThreshold)"
1036 << endl;
1037 TERMM(1, errorMessage.str());
1038 }
1039 }
1040
1041 // Read in optional cut-off boundary information
1042 m_useCutOffBoundaries = false;
1043 fill(m_cutOffBoundaryConditionIds.begin(), m_cutOffBoundaryConditionIds.end(), -1);
1044 // Only do anything if there are cutOffBoundaryConditionIds in the properties
1045 if(Context::propertyExists("cutOffBoundaryConditionIds", m_solverId)) {
1046 // If the property exists, the default is to use the cut-off BCs
1060 m_useCutOffBoundaries = true;
1061 m_useCutOffBoundaries =
1062 Context::getSolverProperty<MBool>("useCutOffBoundaries", m_solverId, AT_, &m_useCutOffBoundaries);
1063
1064 // If it was not explicitly disabled by the user, read in cut-off BC ids
1065 if(m_useCutOffBoundaries) {
1066 for(MInt i = 0; i < 2 * nDim; i++) {
1080 // m_cutOffBoundaryConditionIds[i]
1081 m_cutOffBoundaryConditionIds[i] = Context::getSolverProperty<MInt>("cutOffBoundaryConditionIds", m_solverId,
1082 AT_, &m_cutOffBoundaryConditionIds[i], i);
1083 }
1084 }
1085 }
1086
1096 m_useSourceRampUp = false;
1097 m_useSourceRampUp = Context::getSolverProperty<MBool>("useSourceRampUp", solverId(), AT_, &m_useSourceRampUp);
1098
1099 if(m_useSourceRampUp) {
1109 m_sourceRampUpTime = Context::getSolverProperty<MFloat>("sourceRampUpTime", solverId(), AT_);
1110 }
1111}
1112
1113
1124template <MInt nDim, class SysEqn>
1126 TRACE();
1127
1128 // Count leaf cells to determine number of elements
1129 MInt noElements = 0;
1130 for(MInt cellId = 0; cellId < grid().noInternalCells(); cellId++) {
1131 // Check if element needs to be created
1132 if(needElementForCell(cellId)) {
1133 noElements++;
1134 }
1135 }
1136
1137 // Determine and set max. number of surfaces if not specified in properties
1138 if(m_maxNoSurfaces == -1) {
1139 // TODO labels:DG in some cases with partition level shift the calculated number was too small!
1140 m_maxNoSurfaces = calculateNeededNoSurfaces() + 100;
1141 m_log << "Max. no surfaces was not specified in the properties file (or "
1142 "set to -1). It was therefore calculated automatically for the "
1143 "current domain: "
1144 << m_maxNoSurfaces << endl;
1145 }
1146
1147 if(isActive()) {
1148 MInt maxNoElements = noElements;
1149 MPI_Allreduce(MPI_IN_PLACE, &maxNoElements, 1, type_traits<MInt>::mpiType(), MPI_MAX, mpiComm(), AT_,
1150 "MPI_IN_PLACE", "maxNoElements");
1151 MInt minNoElements = noElements;
1152 MPI_Allreduce(MPI_IN_PLACE, &minNoElements, 1, type_traits<MInt>::mpiType(), MPI_MIN, mpiComm(), AT_,
1153 "MPI_IN_PLACE", "minNoElements");
1154 MInt maxNoSurfaces = m_maxNoSurfaces;
1155 MPI_Allreduce(MPI_IN_PLACE, &maxNoSurfaces, 1, type_traits<MInt>::mpiType(), MPI_MAX, mpiComm(), AT_,
1156 "MPI_IN_PLACE", "maxNoSurfaces");
1157
1158 stringstream message;
1159 message << "Solver #" << solverId() << " - maximum number of DG elements among ranks: " << maxNoElements
1160 << std::endl;
1161 message << "Solver #" << solverId() << " - minimum number of DG elements among ranks: " << minNoElements
1162 << std::endl;
1163 message << "Solver #" << solverId() << " - maximum number of DG surfaces among ranks: " << maxNoSurfaces
1164 << std::endl;
1165 m_log << message.str();
1166 cerr0 << message.str();
1167 }
1168
1169 // Elements (allocated with maximum polynomial degree to support p-ref.)
1170 m_elements.maxPolyDeg(m_maxPolyDeg);
1171 m_elements.maxNoNodes1D(m_maxNoNodes1D);
1172 m_elements.noNodeVars(SysEqn::noNodeVars());
1173 m_elements.reset(noElements);
1174
1175 // Surfaces (allocated with maximum polynomial degree to support p-ref.)
1176 m_surfaces.maxPolyDeg(m_maxPolyDeg);
1177 m_surfaces.maxNoNodes1D(m_maxNoNodes1D);
1178 m_surfaces.noNodeVars(SysEqn::noNodeVars());
1179 m_surfaces.reset(m_maxNoSurfaces);
1180
1181 // Count coarse elements that smaller elements and need an h-element
1182 MInt noHElements = 0;
1183 for(MInt cellId = 0; cellId < grid().noCells(); cellId++) {
1184 if(needHElementForCell(cellId)) {
1185 noHElements++;
1186 }
1187 }
1188
1189 // H-elements
1190 m_helements.reset(noHElements);
1191}
1192
1193template <MInt nDim, class SysEqn>
1195 vector<MFloat> L2Error(m_sysEqn.noVars());
1196 vector<MFloat> LInfError(m_sysEqn.noVars());
1197 vector<MFloat> L2ErrLocal(m_sysEqn.noVars());
1198 vector<MFloat> LInfErrLocal(m_sysEqn.noVars());
1199 calcErrorNorms(m_finalTime, L2Error, LInfError, L2ErrLocal, LInfErrLocal);
1200
1201 cout << "WRITE EOC" << endl;
1202 // Write output for EOC analysis
1203 if(m_calcErrorNorms && isMpiRoot()) {
1204 }
1205}
1206
1219template <MInt nDim, class SysEqn>
1221 TRACE();
1222
1223 RECORD_TIMER_START(m_timers[Timers::Run]);
1224 RECORD_TIMER_START(m_timers[Timers::RunInit]);
1225
1226 // Set polynomial degree, init interpolation, create surfaces etc.
1227 initSolverObjects();
1228
1229 // Apply initial conditions or load restart file
1230 initData();
1231
1232 // Perform remaining steps needed before main loop execution
1233 initMainLoop();
1234
1235 RECORD_TIMER_STOP(m_timers[Timers::RunInit]);
1236
1237 // Run main loop
1238 MBool finalTimeStep = false;
1239 while(!finalTimeStep) {
1240 finalTimeStep = step();
1241 }
1242
1243 // Clean up after the simulation is done
1244 cleanUp();
1245
1246 RECORD_TIMER_STOP(m_timers[Timers::Run]);
1247}
1248
1254template <MInt nDim, class SysEqn>
1256 TRACE();
1257 RECORD_TIMER_START(m_timers[Timers::InitData]);
1258
1259 // Abort if solver not initialized
1260 if(!m_isInitSolver) {
1261 TERMM(1, "Solver was not initialized.");
1262 }
1263
1264 if(m_restart) {
1265 m_log << "Restart is enabled." << endl;
1266
1267 // Load restart file
1268 loadRestartFile();
1269
1270 // Load node variable file
1271 if(SysEqn::noNodeVars() > 0 && !SysEqn::hasTimeDependentNodeVars()) {
1272 loadNodeVarsFile();
1273 }
1274 } else {
1275 // Apply the initial conditions
1276 initialCondition();
1277
1278 // Reset global time step and simulation time
1279 m_timeStep = 0;
1280 m_time = m_startTime;
1281 m_firstTimeStep = true;
1282 }
1283
1284 // Reset current Runge Kutta stage
1285 m_rkStage = 0;
1286
1287 // Update all node variables, if they are used
1288 if(SysEqn::noNodeVars() > 0) {
1289 updateNodeVariables();
1290 // TODO labels:DG,toenhance not required at a restart when loading nodevars, but could be used to overwrite saved
1291 // node vars/change extension
1292 extendNodeVariables();
1293 }
1294
1295 // Set status to initialized
1296 m_isInitData = true;
1297
1298 RECORD_TIMER_STOP(m_timers[Timers::InitData]);
1299}
1300
1301
1306template <MInt nDim, class SysEqn>
1308 TRACE();
1309 RECORD_TIMER_START(m_timers[Timers::InitMainLoop]);
1310
1311 // Abort if data not initialized
1312 if(!m_isInitData) {
1313 TERMM(1, "Solver data was not initialized.");
1314 }
1315
1316 if(!m_restart) {
1317 // Save initial state before entering main loop
1318 if(m_writeInitialSolution) {
1319 saveSolutionFile();
1320 }
1321
1322 // Save node variables for restarting if they are constant in time
1323 if(SysEqn::noNodeVars() > 0 && !SysEqn::hasTimeDependentNodeVars()) {
1324 if(m_writeNodeVarsFile) {
1325 saveNodeVarsFile();
1326 }
1327 }
1328
1329 // Save initial sampling data before entering main loop
1330 m_pointData.save(false);
1331 m_surfaceData.save(false);
1332 m_volumeData.save(false);
1333
1334 // Save inital slice data before entering main loop
1335 m_slice.save(false);
1336 }
1337
1338 // Init end auto save
1339 m_endAutoSaveTime = -1;
1340 m_endAutoSaveWritten = false;
1341 const char* envJobEndTime = getenv("MAIA_JOB_END_TIME");
1342
1343 if(envJobEndTime) {
1344 // Check if env variable only contains digits
1345 const MString jobEndTime(envJobEndTime);
1346 MBool onlyDigits = true;
1347 for(auto&& character : jobEndTime) {
1348 if(!isdigit(character)) {
1349 m_log << "Warning: the environment variable MAIA_JOB_END_TIME "
1350 "included non-digit characters.\n"
1351 << "Saving a restart file before the compute job ends is NOT "
1352 "active."
1353 << endl;
1354 onlyDigits = false;
1355 break;
1356 }
1357 }
1358 // Only activate if MAIA_JOB_END_TIME is a proper integer
1359 if(onlyDigits) {
1360 m_endAutoSaveTime = stoi(jobEndTime);
1361 m_endAutoSaveTime -= m_noMinutesEndAutoSave * 60;
1362 m_log << "Activated automatic restart file writing at " << ctime(&m_endAutoSaveTime)
1363 << " (in Unix time: " << m_endAutoSaveTime << ")." << endl;
1364 }
1365 }
1366
1367 // Output initialization summary
1368 outputInitSummary();
1369
1370 // Init main loop
1371 analyzeTimeStep(m_time, F0, F0, m_timeStep, F0);
1372
1373 // Init timing for run time statistics
1374 m_loopTimeStart = wallTime();
1375 m_analyzeTimeStart = wallTime();
1376 m_outputTime = 0.0;
1377 m_noAnalyzeTimeSteps = 0;
1378
1379 // Set status to initialized
1380 m_isInitMainLoop = true;
1381
1382 RECORD_TIMER_STOP(m_timers[Timers::InitMainLoop]);
1383
1384 // If multiphysics-optimized parallelization is used, prolong and start
1385 // tranmitting
1386 if(g_splitMpiComm) {
1387 RECORD_TIMER_START(m_timers[Timers::MainLoop]);
1388 RECORD_TIMER_START(m_timers[Timers::RungeKuttaStep]);
1389 RECORD_TIMER_START(m_timers[Timers::TimeDeriv]);
1390 prolongToSurfaces();
1391 applyForwardProjection();
1392 startMpiSurfaceExchange();
1393 RECORD_TIMER_STOP(m_timers[Timers::TimeDeriv]);
1394 RECORD_TIMER_STOP(m_timers[Timers::RungeKuttaStep]);
1395 RECORD_TIMER_STOP(m_timers[Timers::MainLoop]);
1396 }
1397}
1398
1399
1413// TODO labels:DG,totest,toremove @ansgar_unified check if everything has been transfered to unified run loop, then
1414// remove
1415template <MInt nDim, class SysEqn>
1417 TRACE();
1418 TERMM(1, "deprecated");
1419
1420 // Abort if main loop not initialized
1421 if(!m_isInitMainLoop) {
1422 TERMM(1, "Main loop was not initialized.");
1423 }
1424
1425 RECORD_TIMER_START(m_timers[Timers::MainLoop]);
1426
1427 startLoadTimer(AT_);
1428
1429 // Check if this is the first Runge-Kutta stage, i.e. start of new timestep
1430 if(m_rkStage == 0) {
1431 // Increment time step
1432 m_timeStep++;
1433
1434 // Calculate time step if it is not already set
1435 if(externalDt < 0.0) {
1436 // Calculate time step at first time step or for correct modulo
1437 if(m_firstTimeStep == true || (m_calcTimeStepInterval > 0 && m_timeStep % m_calcTimeStepInterval == 0)) {
1438 m_dt = calcTimeStep();
1439 m_firstTimeStep = false;
1440 }
1441 } else {
1442 m_dt = externalDt;
1443 }
1444
1445 // Perform adaptive refinement if needed
1446 if(isAdaptationTimeStep(m_timeStep)) {
1447 // Cancel open MPI (receive) requests
1448 cancelMpiRequests();
1449 adaptiveRefinement(m_timeStep);
1450 }
1451 }
1452
1453 // Determine if this is the last time step and reset time step if necessary
1454 MBool finalTimeStep = false;
1455 if(m_finalTime - m_time - m_dt < 1.0E-10) {
1456 finalTimeStep = true;
1457 m_dt = m_finalTime - m_time;
1458 } else if(m_timeStep == m_timeSteps) {
1459 finalTimeStep = true;
1460 } else if(m_timeStep == std::max(m_restartTimeStep, 0) + m_timeSteps) {
1461 // TODO labels:DG @ansgar_unified for coupled combustion case, step() should not be used anymore by other
1462 // run loops!
1463 finalTimeStep = true;
1464 }
1465
1466 if(!substep) {
1467 // Run Runge-Kutta step
1468 timeStepRk(m_time, m_dt);
1469 // Reset current Runge-Kutta stage
1470 m_rkStage = 0;
1471 } else {
1472 // Perform just a single Runge-Kutta stage
1473 timeStepRk(m_time, m_dt, m_rkStage);
1474 // Update current Runge-Kutta stage
1475 m_rkStage = (m_rkStage + 1) % m_noRkStages;
1476 }
1477
1478 stopLoadTimer(AT_);
1479
1480 // Check if time step is completed and return if not
1481 if(m_rkStage != 0) {
1482 RECORD_TIMER_STOP(m_timers[Timers::MainLoop]);
1483 return finalTimeStep;
1484 }
1485
1486 disableDlbTimers();
1487
1488 // Time step completed
1489 // Update physical time and counters
1490 m_time += m_dt;
1491 m_noAnalyzeTimeSteps++;
1492
1493 // Write out solution in intervals
1494 if((m_solutionInterval > 0 && m_timeStep % m_solutionInterval == 0) || (finalTimeStep && m_alwaysSaveFinalSolution)) {
1495 RECORD_TIMER_START(m_timers[Timers::MainLoopIO]);
1496
1497 const MFloat tOutputStart = wallTime();
1498 saveSolutionFile();
1499 const MFloat tOutputEnd = wallTime();
1500
1501 // Update output time so that inner loop does not consider it
1502 m_outputTime += tOutputEnd - tOutputStart;
1503
1504 RECORD_TIMER_STOP(m_timers[Timers::MainLoopIO]);
1505 }
1506
1507 // Write out restart file in intervals
1508 if((m_restartInterval > 0 && m_timeStep % m_restartInterval == 0) || (finalTimeStep && m_alwaysSaveFinalRestart)) {
1509 RECORD_TIMER_START(m_timers[Timers::MainLoopIO]);
1510
1511 const MFloat tOutputStart = wallTime();
1512 saveRestartFile();
1513 const MFloat tOutputEnd = wallTime();
1514
1515 // Update output time so that inner loop does not consider it
1516 m_outputTime += tOutputEnd - tOutputStart;
1517
1518 RECORD_TIMER_STOP(m_timers[Timers::MainLoopIO]);
1519 }
1520
1521 // Check regularly if job is about to end
1522 if(m_endAutoSaveTime != -1 && !m_endAutoSaveWritten && m_timeStep % m_endAutoSaveCheckInterval == 0) {
1523 // synchronize ranks (prevent deadlocks)
1524 MBool writeEndAutoSave = false;
1525 if(isMpiRoot()
1526 && m_endAutoSaveTime
1527 <= chrono::duration_cast<chrono::seconds>(chrono::system_clock::now().time_since_epoch()).count()) {
1528 writeEndAutoSave = true;
1529 }
1530 MPI_Bcast(&writeEndAutoSave, 1, MPI_C_BOOL, 0, mpiComm(), AT_, "writeEndAutoSave");
1531 // Write out restart file if job is about to end
1532 if(writeEndAutoSave) {
1533 RECORD_TIMER_START(m_timers[Timers::MainLoopIO]);
1534
1535 const MFloat tOutputStart = wallTime();
1536 saveRestartFile();
1537 time_t now =
1538 std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
1539 m_log << "Finished end auto save at " << std::ctime(&now) << " (in Unix time: " << now << ")." << endl;
1540 time_t jobEndTime = m_endAutoSaveTime + m_noMinutesEndAutoSave * 60;
1541 m_log << "Job will end at " << std::ctime(&jobEndTime) << " (in Unix time: " << jobEndTime << ")." << endl;
1542
1543 m_endAutoSaveWritten = true;
1544
1545 const MFloat tOutputEnd = wallTime();
1546
1547 // Update output time so that inner loop does not consider it
1548 m_outputTime += tOutputEnd - tOutputStart;
1549
1550 RECORD_TIMER_STOP(m_timers[Timers::MainLoopIO]);
1551 }
1552 }
1553
1554 RECORD_TIMER_START(m_timers[Timers::MainLoopIO]);
1555 // Produce sampling data output
1556 m_pointData.save(finalTimeStep);
1557 m_surfaceData.save(finalTimeStep);
1558 m_volumeData.save(finalTimeStep);
1559 // Produce slice data output
1560 m_slice.save(finalTimeStep);
1561 RECORD_TIMER_STOP(m_timers[Timers::MainLoopIO]);
1562
1563 // Analyze error norms and print info to user
1564 if((m_analysisInterval > 0 && m_timeStep % m_analysisInterval == 0) || (finalTimeStep)) {
1565 RECORD_TIMER_START(m_timers[Timers::Analysis]);
1566
1567 const MFloat timeDiff = wallTime() - m_analyzeTimeStart - m_outputTime;
1568 const MFloat runTimeRelative = timeDiff * noDomains() / m_noAnalyzeTimeSteps / m_statGlobalNoActiveDOFs;
1569 const MFloat runTimeTotal = wallTime() - m_loopTimeStart;
1570
1571 analyzeTimeStep(m_time, runTimeRelative, runTimeTotal, m_timeStep, m_dt);
1572 if(finalTimeStep && isMpiRoot()) {
1573 cout << endl;
1574 cout << "----------------------------------------"
1575 << "----------------------------------------" << endl;
1576 cout << " MAIA finished. Final time: " << m_time << " Time steps: " << m_timeStep << endl;
1577 cout << "----------------------------------------"
1578 << "----------------------------------------" << endl;
1579 }
1580
1581 // Reset time + counters
1582 m_analyzeTimeStart = wallTime();
1583 m_outputTime = 0.0;
1584 m_noAnalyzeTimeSteps = 0;
1585
1586 RECORD_TIMER_STOP(m_timers[Timers::Analysis]);
1587 }
1588 /* } else if (m_aliveInterval > 0 && m_timeStep % m_aliveInterval == 0 && isMpiRoot()) { */
1589 /* // Calculate total run time for printing */
1590 /* const MFloat runTimeTotal = wallTime() - m_loopTimeStart; */
1591
1592 /* // Print out processing information to user */
1593 /* printf("#t/s: %8d | dt: %.4e | Sim. time: %.4e | Run time: %.4e s\n", m_timeStep, m_dt,
1594 * m_time, */
1595 /* runTimeTotal); */
1596 /* } */
1597
1598 RECORD_TIMER_STOP(m_timers[Timers::MainLoop]);
1599
1600 enableDlbTimers();
1601
1602 return finalTimeStep;
1603}
1604
1605
1607template <MInt nDim, class SysEqn>
1609 TRACE();
1610 RECORD_TIMER_START(m_timers[Timers::MainLoop]);
1611
1612 // Abort if main loop not initialized
1613 if(!m_isInitMainLoop) {
1614 TERMM(1, "Main loop was not initialized.");
1615 }
1616
1617 // Check that this is the first Runge-Kutta stage, i.e. start of new timestep
1618 if(m_rkStage != 0) {
1619 TERMM(1,
1620 "preTimeStep should only be called at the start of a new timestep: rkStage = " + std::to_string(m_rkStage));
1621 }
1622
1623 // Increment time step
1624 m_timeStep++;
1625 ASSERT(m_timeStep == globalTimeStep, "Error: time step inconsistent.");
1626
1627 // Calculate time step (or use external dt) at first time step or for correct modulo
1628 if(m_firstTimeStep == true || (m_calcTimeStepInterval > 0 && m_timeStep % m_calcTimeStepInterval == 0)) {
1629 m_dt = calcTimeStep();
1630 m_firstTimeStep = false;
1631 }
1632
1633 // Perform adaptive refinement if needed
1634 if(isAdaptationTimeStep(m_timeStep)) {
1635 // Cancel open MPI (receive) requests
1636 cancelMpiRequests();
1637
1638 adaptiveRefinement(m_timeStep);
1639 }
1640
1641 // Determine if this is the last time step and reset time step size if necessary
1642 m_finalTimeStep = false;
1643 if(m_finalTime - m_time - m_dt < 1.0E-10) {
1644 m_finalTimeStep = true;
1645 m_dt = m_finalTime - m_time;
1646 } else if(g_multiSolverGrid && m_timeStep == std::max(m_restartTimeStep, 0) + m_timeSteps) {
1647 m_finalTimeStep = true;
1648 } else if(!g_multiSolverGrid && m_timeStep == m_timeSteps) {
1649 // TODO labels:DG change default time step behaviour of DG solver?
1650 m_finalTimeStep = true;
1651 }
1652
1653 // Reset external (coupling) sources at beginning of a new time step
1654 resetExternalSources();
1655
1656 RECORD_TIMER_STOP(m_timers[Timers::MainLoop]);
1657}
1658
1659
1661template <MInt nDim, class SysEqn>
1663 TRACE();
1664 RECORD_TIMER_START(m_timers[Timers::MainLoop]);
1665
1666 // Perform just a single Runge-Kutta step/stage
1667 timeStepRk(m_time, m_dt, m_rkStage);
1668 // Update current Runge-Kutta stage
1669 m_rkStage = (m_rkStage + 1) % m_noRkStages;
1670
1671 RECORD_TIMER_STOP(m_timers[Timers::MainLoop]);
1672 // Return if time step is completed
1673 return (m_rkStage == 0);
1674}
1675
1676
1678template <MInt nDim, class SysEqn>
1680 TRACE();
1681 RECORD_TIMER_START(m_timers[Timers::MainLoop]);
1682
1683 // Time step completed
1684 // Update physical time and counters
1685 m_time += m_dt;
1686 m_noAnalyzeTimeSteps++;
1687
1688 // Analyze error norms and print info to user
1689 if((m_analysisInterval > 0 && m_timeStep % m_analysisInterval == 0) || (m_finalTimeStep)) {
1690 RECORD_TIMER_START(m_timers[Timers::Analysis]);
1691
1692 const MFloat timeDiff = wallTime() - m_analyzeTimeStart - m_outputTime;
1693 const MFloat runTimeRelative = timeDiff * noDomains() / m_noAnalyzeTimeSteps / m_statGlobalNoActiveDOFs;
1694 const MFloat runTimeTotal = wallTime() - m_loopTimeStart;
1695
1696 analyzeTimeStep(m_time, runTimeRelative, runTimeTotal, m_timeStep, m_dt);
1697 if(m_finalTimeStep && isMpiRoot()) {
1698 cout << endl;
1699 cout << "----------------------------------------"
1700 << "----------------------------------------" << endl;
1701 cout << " MAIA finished. Final time: " << m_time << " Time steps: " << m_timeStep << endl;
1702 cout << "----------------------------------------"
1703 << "----------------------------------------" << endl;
1704 }
1705
1706 // Reset time + counters
1707 m_analyzeTimeStart = wallTime();
1708 m_outputTime = 0.0;
1709 m_noAnalyzeTimeSteps = 0;
1710
1711 RECORD_TIMER_STOP(m_timers[Timers::Analysis]);
1712 }
1713 // TODO labels:DG,toremove moved to run_unified() but no output of sim-time/dt
1714 /* } else if (m_aliveInterval > 0 && m_timeStep % m_aliveInterval == 0 && isMpiRoot()) { */
1715 /* // Calculate total run time for printing */
1716 /* const MFloat runTimeTotal = wallTime() - m_loopTimeStart; */
1717
1718 /* // Print out processing information to user */
1719 /* printf("#t/s: %8d | dt: %.4e | Sim. time: %.4e | Run time: %.4e s\n", m_timeStep, m_dt,
1720 * m_time, */
1721 /* runTimeTotal); */
1722 /* } */
1723
1724 RECORD_TIMER_STOP(m_timers[Timers::MainLoop]);
1725}
1726
1727
1729template <MInt nDim, class SysEqn>
1730MBool DgCartesianSolver<nDim, SysEqn>::prepareRestart(MBool writeRestart, MBool& NotUsed(writeGridRestart)) {
1731 TRACE();
1732
1733 if(!isActive()) return false;
1734
1735 if((m_restartInterval > 0 && m_timeStep % m_restartInterval == 0) || (m_finalTimeStep && m_alwaysSaveFinalRestart)) {
1736 return true;
1737 }
1738
1739 return writeRestart;
1740}
1741
1742
1744template <MInt nDim, class SysEqn>
1746 const MBool NotUsed(writeBackup),
1747 const MString NotUsed(gridFileName),
1748 MInt* NotUsed(recalcIdTree)) {
1749 TRACE();
1750 CHECK_TIMERS_IO();
1751 RECORD_TIMER_START(m_timers[Timers::MainLoop]);
1752
1753 // Write out restart file
1754 if(writeRestart) {
1755 RECORD_TIMER_START(m_timers[Timers::MainLoopIO]);
1756
1757 const MFloat tOutputStart = wallTime();
1758 saveRestartFile();
1759 const MFloat tOutputEnd = wallTime();
1760
1761 // Update output time so that inner loop does not consider it
1762 m_outputTime += tOutputEnd - tOutputStart;
1763
1764 RECORD_TIMER_STOP(m_timers[Timers::MainLoopIO]);
1765 }
1766
1767 // TODO labels:DG integrate this in the unified run loop/gridcontroller for all solvers!
1768 /* // Check regularly if job is about to end */
1769 /* if (m_endAutoSaveTime != -1 && !m_endAutoSaveWritten */
1770 /* && m_timeStep % m_endAutoSaveCheckInterval == 0) { */
1771 /* // synchronize ranks (prevent deadlocks) */
1772 /* MBool writeEndAutoSave = false; */
1773 /* if (isMpiRoot() */
1774 /* && m_endAutoSaveTime */
1775 /* <= chrono::duration_cast<chrono::seconds>( */
1776 /* chrono::system_clock::now().time_since_epoch()) */
1777 /* .count()) { */
1778 /* writeEndAutoSave = true; */
1779 /* } */
1780 /* MPI_Bcast(&writeEndAutoSave, 1, MPI_C_BOOL, 0, mpiComm(), AT_, "writeEndAutoSave" ); */
1781 /* // Write out restart file if job is about to end */
1782 /* if (writeEndAutoSave) { */
1783 /* RECORD_TIMER_START(m_timers[Timers::MainLoopIO]); */
1784
1785 /* const MFloat tOutputStart = wallTime(); */
1786 /* saveRestartFile(); */
1787 /* time_t now = std::chrono::duration_cast<std::chrono::seconds>( */
1788 /* std::chrono::system_clock::now().time_since_epoch()) */
1789 /* .count(); */
1790 /* m_log << "Finished end auto save at " << std::ctime(&now) */
1791 /* << " (in Unix time: " << now << ")." << endl; */
1792 /* time_t jobEndTime = m_endAutoSaveTime + m_noMinutesEndAutoSave * 60; */
1793 /* m_log << "Job will end at " << std::ctime(&jobEndTime) */
1794 /* << " (in Unix time: " << jobEndTime << ")." << endl; */
1795
1796 /* m_endAutoSaveWritten = true; */
1797
1798 /* const MFloat tOutputEnd = wallTime(); */
1799
1800 /* // Update output time so that inner loop does not consider it */
1801 /* m_outputTime += tOutputEnd - tOutputStart; */
1802
1803 /* RECORD_TIMER_STOP(m_timers[Timers::MainLoopIO]); */
1804 /* } */
1805 /* } */
1806 /* } */
1807
1808 RECORD_TIMER_STOP(m_timers[Timers::MainLoop]);
1809}
1810
1811
1813template <MInt nDim, class SysEqn>
1814void DgCartesianSolver<nDim, SysEqn>::saveSolverSolution(const MBool forceOutput, const MBool finalTimeStep) {
1815 TRACE();
1816 CHECK_TIMERS_IO();
1817 RECORD_TIMER_START(m_timers[Timers::MainLoop]);
1818 RECORD_TIMER_START(m_timers[Timers::MainLoopIO]);
1819
1820 // Write out solution in intervals, at the final time step, or if forced
1821 if((m_solutionInterval > 0 && m_timeStep % m_solutionInterval == 0)
1822 || ((m_finalTimeStep || finalTimeStep) && m_alwaysSaveFinalSolution) || forceOutput) {
1823 const MFloat tOutputStart = wallTime();
1824 saveSolutionFile();
1825 const MFloat tOutputEnd = wallTime();
1826
1827 // Update output time so that inner loop does not consider it
1828 m_outputTime += tOutputEnd - tOutputStart;
1829 }
1830
1831 // Produce sampling data output
1832 m_pointData.save(finalTimeStep);
1833 m_surfaceData.save(finalTimeStep);
1834 m_volumeData.save(finalTimeStep);
1835 // Produce slice data output
1836 m_slice.save(finalTimeStep);
1837
1838 RECORD_TIMER_STOP(m_timers[Timers::MainLoopIO]);
1839 RECORD_TIMER_STOP(m_timers[Timers::MainLoop]);
1840}
1841
1842
1844template <MInt nDim, class SysEqn>
1846 TRACE();
1847 RECORD_TIMER_START(m_timers[Timers::Run]);
1848
1849 // Nothing to be done if solver is not active
1850 if(!isActive()) {
1851 // TODO labels:DG inactive ranks need to call createGridSlice as well
1852 TERMM_IF_COND(m_slice.enabled(), "Fixme: DG slices with inactive ranks not supported.");
1853 return;
1854 }
1855
1856 RECORD_TIMER_START(m_timers[Timers::RunInit]);
1857
1858 // Set polynomial degree, init interpolation, create surfaces etc.
1859 initSolverObjects();
1860
1861 // Apply initial conditions or load restart file
1862 initData();
1863
1864 // Note: moved to finalizeInitSolver, coupler need to be initialized first
1865 // Perform remaining steps needed before main loop execution
1866 // initMainLoop();
1867
1868 RECORD_TIMER_STOP(m_timers[Timers::RunInit]);
1869}
1870
1871
1873template <MInt nDim, class SysEqn>
1875 TRACE();
1876
1877 // Nothing to be done if solver is not active
1878 if(!isActive()) return;
1879
1880 RECORD_TIMER_START(m_timers[Timers::RunInit]);
1881 // Perform remaining steps needed before main loop execution
1882 initMainLoop();
1883 RECORD_TIMER_STOP(m_timers[Timers::RunInit]);
1884}
1885
1886
1894template <MInt nDim, class SysEqn>
1896 TRACE();
1897 RECORD_TIMER_START(m_timers[Timers::InitSolverObjects]);
1898
1899 // NOTE: if anything is added/changed here, make the same changes in balancePre()/balancePost()
1900
1901 // Init grid-to-grid map
1902 initGridMap();
1903
1904 // Init elements
1905 initElements();
1906
1907 // Init h-elements
1908 initHElements();
1909
1910 // Set the polynomial degree in the elements
1911 initPolynomialDegree();
1912
1913 // Calculate all necessary interpolation values
1914 initInterpolation();
1915
1916 // Calculate the coordinates of the integration nodes in the elements
1917 initNodeCoordinates();
1918
1919 // Calculate the inverse Jacobian for the mapping to the reference element
1920 initJacobian();
1921
1922 // Check cell properties
1923 checkCellProperties();
1924
1925 // Create all surfaces (except between internal cells and halo cells)
1926 initSurfaces();
1927
1928 // Initialize all necessary routines & data arrays for MPI communication
1929 if(hasMpiExchange()) {
1930 initMpiExchange();
1931 }
1932
1933 // Check whether sponge is activated
1934 if(useSponge()) {
1935 m_log << "Initializing sponge... ";
1936 sponge().init(m_maxPolyDeg, &grid(), &m_elements, &m_surfaces, &m_boundaryConditions, &m_sysEqn, mpiComm());
1937 m_log << "done" << endl;
1938 }
1939
1940 // For all halo cells send a list of polynomial degrees to neighboring domain
1941 // (p-refinement)
1942 if(hasMpiExchange() && hasPref()) {
1943 exchangeMpiSurfacePolyDeg();
1944 }
1945
1946 // Calculate all necessary mortar projections
1947 initMortarProjections();
1948
1949 // Load points at which the states should be written out
1950 m_pointData.init();
1951 // Load surface points on which the states should be written out
1952 m_surfaceData.init();
1953 // Load volume points on which the states should be written out
1954 m_volumeData.init();
1955
1956 // Load coordinates of slice intercept
1957 m_slice.init();
1958
1959 // Initialize statistics about the simulation itself and write it to m_log
1960 initSimulationStatistics();
1961
1962 // Set status to initialized
1963 m_isInitSolver = true;
1964
1965 RECORD_TIMER_STOP(m_timers[Timers::InitSolverObjects]);
1966}
1967
1968
1973template <MInt nDim, class SysEqn>
1975 TRACE();
1976
1990 // Return fast if no donor grid is specified
1991 if(!Context::propertyExists("donorGridFileName", m_solverId)) {
1992 return;
1993 }
1994
2008 // Return if no grid map should be loaded
2009 if(!(Context::getSolverProperty<MBool>("loadGridMap", m_solverId, AT_))) {
2010 return;
2011 }
2012
2026 // Get grid map file name if specified
2027 MString gridMapFileName = outputDir() + "gridmap" + ParallelIo::fileExt();
2028 gridMapFileName = Context::getSolverProperty<MString>("gridMapFileName", m_solverId, AT_, &gridMapFileName);
2029 // Load grid map file (grid map was already generated in CartesianGrid)
2030 loadGridMap(gridMapFileName);
2031
2045 // Check grid map if desired
2046 MBool check = true;
2047 check = Context::getSolverProperty<MBool>("checkGridMap", m_solverId, AT_, &check);
2048 if(check) {
2049 // Get name of donor grid file
2050 const MString donorGridFileName = Context::getSolverProperty<MString>("donorGridFileName", m_solverId, AT_);
2051
2052 checkGridMap(donorGridFileName);
2053 }
2054}
2055
2056
2063template <MInt nDim, class SysEqn>
2065 TRACE();
2066
2067 m_log << "Loading grip map from " << gridMapFileName << "..." << endl;
2068
2069 // Read grid map
2070 const MInt noCells = grid().noInternalCells();
2071 MIntScratchSpace gridMap(noCells, AT_, "gridMap");
2072 MIntScratchSpace gridMapNoOffspring(noCells, AT_, "gridMapNoOffspring");
2073 using namespace parallel_io;
2074 ParallelIo gridMapFile(gridMapFileName, PIO_READ, mpiComm());
2075 gridMapFile.setOffset(noCells, grid().domainOffset(domainId()));
2076 gridMapFile.readArray(&gridMap[0], "cellIds");
2077 gridMapFile.readArray(&gridMapNoOffspring[0], "noOffspring");
2078
2079 // Temporary storage for the number of offspring cells mapped to target cells
2080 MIntScratchSpace noOffspring(noCells, AT_, "noOffspring");
2081
2082 // Clear previous grid map offsets
2083 m_gridMapOffsets.clear();
2084
2085 // Determine grid map offsets
2086 for(MInt cellId = 0; cellId < noCells; cellId++) {
2087 // Skip if mapped donor cell id is -1, i.e., if there is no donor cell
2088 if(gridMap[cellId] == -1) {
2089 continue;
2090 }
2091
2092 // Store first local target cell id and first mapped donor cell global id
2093 const MInt firstTargetCellId = cellId;
2094 const MInt firstDonorGlobalId = gridMap[cellId];
2095
2096 // Store the number of donor offspring cells for the first considered target
2097 // cell
2098 noOffspring[0] = gridMapNoOffspring[cellId];
2099
2100 // Find consecutive mapped ids (account for child cells on the donor grid),
2101 // i.e., consecutive cells on the target grid that have one or more donor
2102 // cells
2103 MInt noDonorCells = 1 + gridMapNoOffspring[cellId];
2104 while(cellId + 1 < noCells) {
2105 if(gridMap[cellId + 1] == firstDonorGlobalId + noDonorCells) {
2106 // If next mapped target cell id is contiguous, increase donor size and
2107 // proceed with next
2108 cellId++;
2109 noDonorCells += 1 + gridMapNoOffspring[cellId];
2110 noOffspring[cellId - firstTargetCellId] = gridMapNoOffspring[cellId];
2111 } else if(gridMap[cellId + 1] == firstDonorGlobalId + noDonorCells - 1) {
2112 // One-to-many mapping: the next target cell is also mapped to the
2113 // previous donor cell.
2114 cellId++;
2115 // Set number of offspring to -1 to indicate one-to-many mapping
2116 noOffspring[cellId - firstTargetCellId] = -1;
2117 } else {
2118 // Otherwise exit while loop
2119 break;
2120 }
2121 }
2122
2123 // Determine number of contiguous mapped target cells
2124 const MInt noTargetCells = cellId - firstTargetCellId + 1;
2125
2126 // Create & store grid map offset
2127 m_gridMapOffsets.push_back({firstTargetCellId, noTargetCells, firstDonorGlobalId, noDonorCells,
2128 std::vector<MInt>(noOffspring.begin(), noOffspring.end())});
2129 }
2130
2131 m_log << "Found " << m_gridMapOffsets.size() << " contiguous mapped region(s):" << endl;
2132 for(size_t i = 0; i < m_gridMapOffsets.size(); i++) {
2133 const auto& o = m_gridMapOffsets[i];
2134 m_log << "- offset: " << i << "; firstTargetCellId: " << o.m_firstTargetCellId
2135 << "; globalId: " << grid().tree().globalId(o.m_firstTargetCellId) << "; noDonorCells: " << o.m_noDonorCells
2136 << "; noTargetCells: " << o.m_noTargetCells << "; firstDonorGlobalId: " << o.m_firstDonorGlobalId << endl;
2137 }
2138
2139 // Determine maximum amount of offsets across all domains
2140 m_maxNoGridMapOffsets = m_gridMapOffsets.size();
2141 MPI_Allreduce(MPI_IN_PLACE, &m_maxNoGridMapOffsets, 1, type_traits<MInt>::mpiType(), MPI_MAX, mpiComm(), AT_,
2142 "MPI_IN_PLACE", "m_maxNoGridMapOffsets");
2143 m_log << "Maximum number of grid map offsets on all domains: " << m_maxNoGridMapOffsets << endl;
2144}
2145
2146
2156template <MInt nDim, class SysEqn>
2158 TRACE();
2159
2160 //------------------------------
2161 //------------------------------
2162 //------------------------------
2163 if(domainId() == 0) {
2164 cerr << "Warning: DgCartesianSolver::checkGridMap deactivated! (fix coordinate comparison)" << endl;
2165 }
2166 return;
2167 //------------------------------
2168 //------------------------------
2169 //------------------------------
2170
2171 m_log << "Checking grip map for donor grid " << donorGridFileName << "...";
2172 const MFloat startTime = wallTime();
2173
2174 // Determine the local number of donor cells and the maximum number of donor
2175 // cells across all grid map offsets on this domain
2176 MInt noDonorCellsLocal = 0;
2177 MInt gridMapMaxNoDonorCells = 0;
2178 for(auto&& offset : m_gridMapOffsets) {
2179 noDonorCellsLocal += offset.m_noDonorCells;
2180 gridMapMaxNoDonorCells = max(gridMapMaxNoDonorCells, offset.m_noDonorCells);
2181 }
2182
2183
2185 // Check 1: total number of donor cells matches number of cells in grid file
2187 using namespace maia::parallel_io;
2188 ParallelIo file(donorGridFileName, PIO_READ, mpiComm());
2189 MInt noDonorCellsFile;
2190 // file.readScalar(&noDonorCellsFile, "noCells");
2191 file.getAttribute(&noDonorCellsFile, "noCells");
2192 MInt noDonorCellsGlobal;
2193 MPI_Allreduce(&noDonorCellsLocal, &noDonorCellsGlobal, 1, type_traits<MInt>::mpiType(), MPI_SUM, mpiComm(), AT_,
2194 "noDonorCellsLocal", "noDonorCellsGlobal");
2195 if(noDonorCellsFile != noDonorCellsGlobal) {
2196 TERMM(1, "Number of mapped donor cells does not match number of cells in "
2197 "donor grid file: mapped: "
2198 + to_string(noDonorCellsGlobal) + "; grid: " + to_string(noDonorCellsFile));
2199 }
2200
2201
2203 // Check 2: coordinates of donor cells match those of target cells
2205 // Define epsilon according to CartesianGrid constructor
2206 const MFloat eps = 1.0 / FPOW2(30) * grid().lengthLevel0();
2207
2208 // Successively read coordinates of donor grid and check them against target
2209 // grid coordinates
2210 MFloatScratchSpace coordinates(max(gridMapMaxNoDonorCells, 1), AT_, "coordinates");
2211 const array<MString, 3> dirs = {{"x", "y", "z"}};
2212 for(MInt offsetId = 0; offsetId < m_maxNoGridMapOffsets; offsetId++) {
2213 // Store whether this is a valid iteration or if this is just done to make
2214 // sure that reading from the grid file in parallel works correctly
2215 const MBool valid = (static_cast<size_t>(offsetId) < m_gridMapOffsets.size());
2216
2217 // Set offset dependent on whether this is a valid iteration
2218 const MInt noDonorCells = valid ? m_gridMapOffsets[offsetId].m_noDonorCells : 0;
2219 const MInt firstDonorGlobalId = valid ? m_gridMapOffsets[offsetId].m_firstDonorGlobalId : 0;
2220 file.setOffset(noDonorCells, firstDonorGlobalId);
2221
2222 // Read and compare coordinates
2223 for(MInt dir = 0; dir < nDim; dir++) {
2224 // Read from file
2225 file.readArray(&coordinates[0], "coordinates_" + to_string(dir));
2226
2227 // Skip comparison if not a valid iteration
2228 if(!valid) {
2229 continue;
2230 }
2231
2232 const MInt noTargetCells = m_gridMapOffsets[offsetId].m_noTargetCells;
2233 MInt donorCellId = 0;
2234 // Initialize donorLength to bogus value s.t. it always triggers an abort
2235 // if this value is (erroneously) used before a valid length is set
2236 MFloat donorLength = -numeric_limits<MFloat>::infinity();
2237
2238 // Compare coordinates
2239 for(MInt i = 0; i < noTargetCells; i++) {
2240 const MInt cellId = m_gridMapOffsets[offsetId].m_firstTargetCellId + i;
2241 const MFloat targetCoordinate = grid().tree().coordinate(cellId, dir);
2242
2243 // Check if this element belongs to a one-to-many mapping
2244 if(m_gridMapOffsets[offsetId].m_noOffspring[i] == -1) {
2245 const MFloat lastDonorCoordinate = coordinates[donorCellId - 1];
2246 // Check if target cell is contained in the donor cell, which is the
2247 // same as the last considered and matching donor cell, for which the
2248 // cell length is stored in 'donorLength'.
2249 if(targetCoordinate < lastDonorCoordinate - 0.5 * donorLength
2250 || targetCoordinate > lastDonorCoordinate + 0.5 * donorLength) {
2251 TERMM(1, "Target cell of one-to-many mapping not contained in "
2252 "donor cell.");
2253 }
2254 // Continue with next target cell
2255 continue;
2256 }
2257
2258 // Compare coordinates of matching target and donor cell
2259 const MFloat donorCoordinate = coordinates[donorCellId];
2260 if(!approx(targetCoordinate, donorCoordinate, eps)) {
2261 const MInt globalId = grid().tree().globalId(cellId);
2262 TERMM(1, dirs[dir] + "-coordinates for target cell " + to_string(cellId) + " (globalId: "
2263 + to_string(globalId) + ") and donor cell " + to_string(firstDonorGlobalId + donorCellId)
2264 + " do not match: target: " + to_string(targetCoordinate)
2265 + "; donor: " + to_string(donorCoordinate));
2266 }
2267 donorCellId++;
2268
2269 const MFloat targetLength = grid().cellLengthAtCell(cellId);
2270 // Store cell length of matching donor cell for one-to-many check
2271 donorLength = targetLength;
2272
2273 // Check if donor offspring cells are contained in target cell
2274 for(MInt offspringId = 0; offspringId < m_gridMapOffsets[offsetId].m_noOffspring[i]; offspringId++) {
2275 const MFloat offspringCoordinate = coordinates[donorCellId + offspringId];
2276 if(offspringCoordinate < targetCoordinate - 0.5 * targetLength
2277 || offspringCoordinate > targetCoordinate + 0.5 * targetLength) {
2278 TERMM(1, "Donor offspring not contained in target cell.");
2279 }
2280 }
2281 // Increase by the number of donor child cells for this target cell
2282 donorCellId += m_gridMapOffsets[offsetId].m_noOffspring[i];
2283 }
2284 }
2285 }
2286
2287 // Show duration to allow estimate about how costly this check was
2288 m_log << "OK (duration: " << (wallTime() - startTime) << " s)" << endl;
2289}
2290
2291
2297template <MInt nDim, class SysEqn>
2299 TRACE();
2300
2301 m_log << "Initializing elements... ";
2302 for(MInt cellId = 0; cellId < grid().noInternalCells(); cellId++) {
2303 // Check if element needs to be created
2304 if(needElementForCell(cellId)) {
2305 createElement(cellId);
2306 }
2307 }
2308 m_log << "done" << endl;
2309}
2310
2316template <MInt nDim, class SysEqn>
2318 TRACE();
2319
2320 m_log << "Initializing h-refined elements... ";
2321
2322 for(MInt cellId = 0; cellId < grid().noInternalCells(); cellId++) {
2323 // Check if h-element needs to be created
2324 if(needHElementForCell(cellId)) {
2325 createHElement(cellId);
2326 }
2327 }
2328
2329 m_log << "done" << endl;
2330}
2331
2332
2341template <MInt nDim, class SysEqn>
2343 // TRACE();
2344
2345 MBool needElement = true;
2346
2347 // Do not create element if...
2348 // ... cell has child cells or
2349 if(grid().tree().hasChildren(cellId)
2350 // ... cell center is outside the geometry
2351 || !pointIsInside(&grid().tree().coordinate(cellId, 0))) {
2352 needElement = false;
2353 }
2354 return needElement;
2355}
2356
2357
2366template <MInt nDim, class SysEqn>
2368 // TRACE();
2369
2370 // Only leaf cells have elements
2371 if(grid().tree().hasChildren(cellId)) {
2372 return false;
2373 }
2374
2375 // Check neighbors in every direction. If neighbor has children, then neighbor
2376 // is h-refined and coarse element requires an h-element
2377 for(MInt dir = 0; dir < 2 * nDim; dir++) {
2378 if(grid().tree().hasNeighbor(cellId, dir)) {
2379 const MInt neighborId = grid().tree().neighbor(cellId, dir);
2380 if(grid().tree().hasChildren(neighborId)) {
2381 return true;
2382 }
2383 }
2384 }
2385
2386 return false;
2387}
2388
2389
2397template <MInt nDim, class SysEqn>
2399 TRACE();
2400
2401 // Loop over all internal cells
2402 MInt noSurfaces = 0;
2403 for(MInt cellId = 0; cellId < grid().noInternalCells(); cellId++) {
2404 // Skip cell if it is not a leaf cell (i.e. if it has child cells)
2405 if(grid().tree().hasChildren(cellId)) {
2406 continue;
2407 }
2408
2409 // Loop over all directions to check if surfaces need to be created
2410 for(MInt dir = 0; dir < 2 * nDim; dir++) {
2411 if(!grid().tree().hasNeighbor(cellId, dir)) {
2412 // If it does not have a neighbor, cell is boundary cell or neighbor is
2413 // coarse, thus one surface is needed
2414 noSurfaces++;
2415 } else {
2416 // Otherwise check if surface needs to be created
2417 const MInt neighborId = grid().tree().neighbor(cellId, dir);
2418 const MBool neighborIsHalo = (neighborId >= grid().noInternalCells());
2419
2420 if(grid().tree().hasChildren(neighborId)) {
2421 // Neighbor is refined
2422 if(neighborIsHalo) {
2423 // Only create surface towards refined cells if neighbor is halo
2424 // cell (in this case 2 ^ (nDim - 1) surfaces are needed). This
2425 // slightly overestimates the actual number of needed surfaces in
2426 // case no all refined cells exist
2427 noSurfaces += IPOW2(nDim - 1);
2428 }
2429 } else {
2430 // Neighbor is at same level
2431 if(dir % 2 == 1 || neighborIsHalo) {
2432 // Count surfaces only in positive direction or if neighbor is halo
2433 // cell
2434 noSurfaces++;
2435 }
2436 }
2437 }
2438 }
2439 }
2440
2441 return noSurfaces;
2442}
2443
2444
2459template <MInt nDim, class SysEqn>
2461 // TRACE();
2462
2463 // Get bounding box of grid
2464 const MFloat* const box = geometry().boundingBox();
2465
2466 // Shoot rays in each direction
2467 MBool isInside = true;
2468 for(MInt dir = 0; dir < nDim; dir++) {
2469 // Cast ray from the point in question in the -ve 'dir'-direction
2470 array<MFloat, 2 * nDim> target;
2471 for(MInt i = 0; i < nDim; i++) {
2472 target[i] = coordinates[i];
2473 target[i + nDim] = coordinates[i];
2474 }
2475 // This line ensures that the second point is well outside the bounding box
2476 target[dir] = box[dir] - (target[dir] - box[dir]);
2477
2478 // Get list of intersecting geometry elements
2479 std::vector<MInt> nodeList;
2480 geometry().getLineIntersectionElements(&target[0], nodeList);
2481
2482 // Point is inside iff the number of cuts is uneven
2483 if(nodeList.size() % 2 == 0) {
2484 isInside = false;
2485 break;
2486 }
2487 }
2488
2489 return isInside;
2490}
2491
2492
2499template <MInt nDim, class SysEqn>
2501 // TRACE();
2502
2503 // Determine element id and create element in collector
2504 const MInt elementId = m_elements.size();
2505 m_elements.append();
2506
2507 // Set cell id for forward references to cells
2508 m_elements.cellId(elementId) = cellId;
2509
2510 // Reset surface ids
2511 for(MInt dir = 0; dir < 2 * nDim; dir++) {
2512 m_elements.surfaceIds(elementId, dir) = -1;
2513 }
2514}
2515
2522template <MInt nDim, class SysEqn>
2524 // TRACE();
2525
2526 // Determine h-element id and create h-element in collector
2527 const MInt hElementId = m_helements.size();
2528 m_helements.append();
2529
2530 // Set elementId so that it can be used in loops over the h-element collector
2531 m_helements.elementId(hElementId) = m_elements.getElementByCellId(cellId);
2532
2533 // Reset h-refined surface ids
2534 for(MInt dir = 0; dir < 2 * nDim; dir++) {
2535 for(MInt pos = 0; pos < 2 * (nDim - 1); pos++) {
2536 m_helements.hrefSurfaceIds(hElementId, dir, pos) = -1;
2537 }
2538 }
2539}
2540
2541
2548template <MInt nDim, class SysEqn>
2550 TRACE();
2551
2552 m_log << "Initializing polynomial degree and number of nodes in the elements... ";
2553
2554 if(!hasPref()) {
2555 fill_n(&m_elements.polyDeg(0), m_elements.size(), m_initPolyDeg);
2556 fill_n(&m_elements.noNodes1D(0), m_elements.size(), m_initNoNodes1D);
2557 } else {
2558 // If p-refinement is used, select method based on property settings
2559 initPrefinement();
2560 }
2561
2562 // Once the polynomial degree is set, calculate the total data size and number
2563 // of nodes on this domain.
2564 // The internal data size counts the total number of MFloats allocated for
2565 // the conservative variables
2566 m_internalDataSize = m_elements.size() * m_elements.maxNoNodesXD() * SysEqn::noVars();
2567
2568 // The total number of nodes counts the number of nodes that are in use, i.e.
2569 // not counting unused but allocated nodes for elements with a polynomial
2570 // degree less than the maximum
2571 m_noTotalNodesXD = 0;
2572 for(MInt i = 0; i < m_elements.size(); i++) {
2573 m_noTotalNodesXD += m_elements.noNodesXD(i);
2574 }
2575
2576 m_log << "done" << endl;
2577}
2578
2579
2584template <MInt nDim, class SysEqn>
2586 TRACE();
2587
2588 m_log << "Static p-refinement is enabled." << endl;
2589
2590 MIntScratchSpace changed(m_elements.size(), AT_, "changed");
2591 fill(changed.begin(), changed.end(), 0);
2592 const MInt noElements = m_elements.size();
2593
2594 for(MInt elementId = 0; elementId < noElements; elementId++) {
2595 const MInt cellId = m_elements.cellId(elementId);
2596
2597 // Static p-refinement is done through patches that specify a region and a
2598 // polynomial degree. Elements that are in two or more patches get the
2599 // polynomial degree of the last matching patch.
2600
2601 // Initialize polynomial degree with initPolyDeg, which is used if no
2602 // matching patch is found
2603 MInt elemPolyDeg = m_initPolyDeg;
2604 MInt elemNoNodes1D = m_initNoNodes1D;
2605
2606 // Iterate over all defined patches to check if they contain the current
2607 // cell
2608 for(std::vector<MFloat>::size_type patchId = 0; patchId < m_prefPatchesPolyDeg.size(); patchId++) {
2609 MBool inPatch = true;
2610
2611 for(MInt i = 0; i < nDim; i++) {
2612 const MFloat cellCoord = grid().tree().coordinate(cellId, i);
2613
2614 if(cellCoord < m_prefPatchesCoords[patchId][i]) {
2615 inPatch = false;
2616 }
2617 if(cellCoord > m_prefPatchesCoords[patchId][i + nDim]) {
2618 inPatch = false;
2619 }
2620 }
2621
2622 // Set the new polynomial degree if the element lies inside the patch
2623 if(inPatch) {
2624 elemPolyDeg = m_prefPatchesPolyDeg[patchId];
2625 elemNoNodes1D = m_prefPatchesNoNodes1D[patchId];
2626
2627 changed[elementId]++;
2628 }
2629 }
2630 m_elements.polyDeg(elementId) = elemPolyDeg;
2631 m_elements.noNodes1D(elementId) = elemNoNodes1D;
2632 }
2633
2634 m_log << "p-refinement"
2635 << " changed the polynomial degree "
2636 << "on " << count_if(changed.begin(), changed.end(), [](MInt i) { return i > 0; }) << " elements in total for "
2637 << accumulate(changed.begin(), changed.end(), 0) << " times." << endl;
2638}
2639
2640
2648template <MInt nDim, class SysEqn>
2650 TRACE();
2651
2652 m_log << "Initializing interpolation data... ";
2653
2654 // Interpolation
2655 m_interpolation.clear();
2656 m_interpolation.resize(m_maxPolyDeg + 1);
2657 for(MInt polyDeg = m_minPolyDeg; polyDeg <= m_maxPolyDeg; polyDeg++) {
2658 m_interpolation[polyDeg].clear();
2659 m_interpolation[polyDeg].resize(m_maxNoNodes1D + 1);
2660 }
2661
2662 // Convert integers to enums
2663 auto polyType = static_cast<DgPolynomialType>(m_dgPolynomialType);
2664 auto intMethod = static_cast<DgIntegrationMethod>(m_dgIntegrationMethod);
2665
2666 // Create DgInterpolation object for each possible occurring polynomial
2667 // degree AND number of nodes (trivial for DG, necessary for SBP)
2668
2669 for(MInt i = m_minPolyDeg; i <= m_maxPolyDeg; i++) {
2670 MInt prefIndex = -1;
2671 for(std::vector<MFloat>::size_type j = 0; j < m_prefPatchesPolyDeg.size(); j++) {
2672 if(i == (MInt)m_prefPatchesPolyDeg[j]) {
2673 prefIndex = j;
2674 break;
2675 }
2676 }
2677
2678 MString refOperator = m_sbpOperator;
2679 MInt refNoNodes1D = m_sbpMode ? m_initNoNodes1D : (i + 1);
2680 if(prefIndex != -1) {
2681 refOperator = m_prefPatchesOperators[prefIndex];
2682 refNoNodes1D = m_prefPatchesNoNodes1D[prefIndex];
2683 }
2684 m_interpolation[i][refNoNodes1D].init(i, polyType, refNoNodes1D, intMethod, m_sbpMode, refOperator);
2685 }
2686
2687 // Local and global volume
2688 // Calculate the local and global volume for use in error normalizations
2689 m_globalVolume = F0;
2690 m_localVolume = F0;
2691 for(MInt elementId = 0; elementId < m_elements.size(); elementId++) {
2692 const MInt cellId = m_elements.cellId(elementId);
2693 m_localVolume += pow(grid().cellLengthAtCell(cellId), nDim);
2694 }
2695
2696 RECORD_TIMER_START(m_timers[Timers::Accumulated]);
2697 RECORD_TIMER_START(m_timers[Timers::MPI]);
2698 RECORD_TIMER_START(m_timers[Timers::MPIComm]);
2699 MPI_Allreduce(&m_localVolume, &m_globalVolume, 1, MPI_DOUBLE, MPI_SUM, mpiComm(), AT_, "m_localVolume",
2700 "m_globalVolume");
2701 RECORD_TIMER_STOP(m_timers[Timers::MPIComm]);
2702 RECORD_TIMER_STOP(m_timers[Timers::MPI]);
2703 RECORD_TIMER_STOP(m_timers[Timers::Accumulated]);
2704
2705 // Init interpolation for error analysis
2706 if(m_calcErrorNorms) {
2707 // Init interpolation object for error analysis
2708 m_interpAnalysis.init(m_polyDegAnalysis, polyType, m_noAnalysisNodes, intMethod, m_sbpMode, m_sbpOperator);
2709
2710 // Create volume weights for error analysis
2711 const MInt noNodesAnalysis1D = m_noAnalysisNodes;
2712 const MInt noNodesAnalysis1D3 = (nDim == 3) ? noNodesAnalysis1D : 1;
2713 m_wVolumeAnalysis.resize(noNodesAnalysis1D, noNodesAnalysis1D, noNodesAnalysis1D3);
2714 const MFloatVector& wInt = m_interpAnalysis.m_wInt;
2715 for(MInt i = 0; i < noNodesAnalysis1D; i++) {
2716 for(MInt j = 0; j < noNodesAnalysis1D; j++) {
2717 for(MInt k = 0; k < noNodesAnalysis1D3; k++) {
2718 m_wVolumeAnalysis(i, j, k) = wInt[i] * wInt[j] * (nDim == 3 ? wInt[k] : F1);
2719 }
2720 }
2721 }
2722
2723 // Create analysis Vandermonde matrices to interpolate the solution from the
2724 // calculation nodes to the analysis nodes
2725 m_vdmAnalysis.clear();
2726 m_vdmAnalysis.resize(m_maxPolyDeg + 1);
2727 for(MInt polyDeg = m_minPolyDeg; polyDeg <= m_maxPolyDeg; polyDeg++) {
2728 m_vdmAnalysis[polyDeg].clear();
2729 m_vdmAnalysis[polyDeg].resize(m_maxNoNodes1D + 1);
2730 }
2731
2732
2733 // Create matrices
2734 for(MInt polyDeg = m_minPolyDeg; polyDeg <= m_maxPolyDeg; polyDeg++) {
2735 if(m_sbpMode) {
2736 for(MInt noNodes1D = m_minNoNodes1D; noNodes1D <= m_maxNoNodes1D; noNodes1D++) {
2737 m_vdmAnalysis[polyDeg][noNodes1D].resize(m_noAnalysisNodes, noNodes1D);
2738 const DgInterpolation& interp = m_interpolation[polyDeg][noNodes1D];
2739 ASSERT(noNodes1D == m_noAnalysisNodes,
2740 "For now SBP Error Analysis only supports direct evaluation at regular nodes "
2741 "without interpolation...(noNodes1D = "
2742 + to_string(noNodes1D) + ", noAnalysisNodes = " + to_string(m_noAnalysisNodes) + ")");
2743
2745 &interp.m_nodes[0],
2746 m_noAnalysisNodes,
2747 &m_interpAnalysis.m_nodes[0],
2748 &m_vdmAnalysis[polyDeg][noNodes1D][0]);
2749 }
2750 } else {
2751 const MInt noNodes1D = polyDeg + 1;
2752 m_vdmAnalysis[polyDeg][noNodes1D].resize(m_noAnalysisNodes, noNodes1D);
2753 const DgInterpolation& interp = m_interpolation[polyDeg][noNodes1D];
2755 &interp.m_nodes[0],
2756 m_noAnalysisNodes,
2757 &m_interpAnalysis.m_nodes[0],
2758 &interp.m_wBary[0],
2759 &m_vdmAnalysis[polyDeg][noNodes1D][0]);
2760 }
2761 }
2762 }
2763
2764 m_log << "done" << endl;
2765}
2766
2774template <MInt nDim, class SysEqn>
2776 TRACE();
2777
2778 m_log << "Initializing node coordinates... ";
2779
2780 const MInt noElements = m_elements.size();
2781
2782 // Iterate over all elements and calculate the integration node coordinates
2783 for(MInt elementId = 0; elementId < noElements; elementId++) {
2784 calcElementNodeCoordinates(elementId);
2785 }
2786
2787 m_log << "done" << endl;
2788}
2789
2790
2798template <MInt nDim, class SysEqn>
2800 // Create a shallow tensor for the node coordinates
2801 const MInt cellId = m_elements.cellId(elementId);
2802 const MInt lvl = grid().tree().level(cellId);
2803 const MInt polyDeg = m_elements.polyDeg(elementId);
2804 const MInt noNodes1D = m_elements.noNodes1D(elementId);
2805 const MInt noNodes1D3 = (nDim == 3) ? noNodes1D : 1;
2806 MFloatTensor nodeCoordinates(&m_elements.nodeCoordinates(elementId), noNodes1D, noNodes1D, noNodes1D3, nDim);
2807
2808 // Iterate over all nodes and set the coordinates
2809 for(MInt i = 0; i < noNodes1D; i++) {
2810 for(MInt j = 0; j < noNodes1D; j++) {
2811 for(MInt k = 0; k < noNodes1D3; k++) {
2812 // Node coordinates = cell center
2813 // + 1/2 cell length * normalized ([-1,1]) node coordinate
2814 nodeCoordinates(i, j, k, 0) =
2815 grid().tree().coordinate(cellId, 0)
2816 + F1B2 * grid().cellLengthAtLevel(lvl) * m_interpolation[polyDeg][noNodes1D].m_nodes[i];
2817 nodeCoordinates(i, j, k, 1) =
2818 grid().tree().coordinate(cellId, 1)
2819 + F1B2 * grid().cellLengthAtLevel(lvl) * m_interpolation[polyDeg][noNodes1D].m_nodes[j];
2820 IF_CONSTEXPR(nDim == 3) {
2821 nodeCoordinates(i, j, k, 2) =
2822 grid().tree().coordinate(cellId, 2)
2823 + F1B2 * grid().cellLengthAtLevel(lvl) * m_interpolation[polyDeg][noNodes1D].m_nodes[k];
2824 }
2825 }
2826 }
2827 }
2828}
2829
2830
2844template <MInt nDim, class SysEqn>
2846 TRACE();
2847
2848 m_log << "Initializing inverse Jacobian determinant... ";
2849
2850 const MInt noElements = m_elements.size();
2851 MFloat* invJacobians = &m_elements.invJacobian(0);
2852
2853 for(MInt i = 0; i < noElements; i++) {
2854 const MInt cellId = m_elements.cellId(i);
2855 invJacobians[i] = F2 / grid().cellLengthAtCell(cellId);
2856 }
2857
2858 m_log << "done" << endl;
2859}
2860
2861
2869template <MInt nDim, class SysEqn>
2871 TRACE();
2872
2873 m_log << "Initializing cell properties... ";
2874
2875 const MInt noCells = grid().noCells();
2876
2877 // Make sure that all cells that are not internal cells are marked as halo, as this is an
2878 // implicit assumption used throughout the solver
2879 for(MInt c = grid().noInternalCells(); c < noCells; c++) {
2880 if(!grid().tree().hasProperty(c, Cell::IsHalo)) {
2881 TERMM(1, "Cell " + to_string(c) + " should be marked as a halo cell");
2882 }
2883 }
2884
2885 m_log << "done" << endl;
2886}
2887
2892template <MInt nDim, class SysEqn>
2894 TRACE();
2895
2896 const MInt noElements = m_elements.size();
2897 const MInt noDirs = 2 * nDim;
2898 const MInt* const cellIds = &m_elements.cellId(0);
2899
2901 // 1) Create boundary surfaces
2903 m_log << "Creating boundary surfaces... ";
2904 // m_boundarySurfacesOffset = m_surfaces.size();
2905 m_noBoundarySurfaces = 0;
2906
2907 // Identify all interface cells, i.e., cells that are intersected by at least one geometry
2908 // element
2909 MBoolScratchSpace isInterface(grid().tree().size(), AT_, "isInterface");
2910 this->identifyBoundaryCells(&isInterface[0]);
2911
2912 // Delete m_geometryIntersection if already allocated
2913 if(m_geometryIntersection) {
2914 delete m_geometryIntersection;
2915 }
2916 // Initialize geometry intersection object
2917 m_geometryIntersection = new GeometryIntersection<nDim>(&grid(), &geometry());
2918
2919 // Loop over all elements and all directions and check if boundary
2920 // surfaces have to be created and which boundary condition ids are present
2921 MIntScratchSpace bcIds(noElements, noDirs, AT_, "bcIds");
2922 fill(bcIds.begin(), bcIds.end(), -1);
2923 for(MInt elementId = 0; elementId < noElements; elementId++) {
2924 // Skip elements that are not at an interface
2925 const MInt cellId = cellIds[elementId];
2926 if(!isInterface[cellId]) {
2927 continue;
2928 }
2929
2930 // Get boundary condition ids and save them to scratch
2931 array<MInt, 2 * nDim> ids = getBoundaryConditionIds(cellId);
2932
2933 // Check for neighboring elements and prevent creating a boundary surface
2934 for(MInt dir = 0; dir < noDirs; dir++) {
2935 const MInt nId = grid().tree().neighbor(cellId, dir);
2936 if(ids[dir] > -1 && nId > -1 && needElementForCell(nId)) {
2937 ids[dir] = -1;
2938 }
2939 }
2940
2941 copy(ids.begin(), ids.end(), &bcIds(elementId, 0));
2942 }
2943
2944 // Find additional boundary conditions for boundary elements that are not
2945 // intersected by geometry but that do not have a neighbor. This is mainly
2946 // necessary to support concave domains and situations where the intersected
2947 // cell does not have an element because the cell center is outside of the
2948 // domain.
2949 for(MInt elementId = 0; elementId < noElements; elementId++) {
2950 const MInt cellId = cellIds[elementId];
2951 for(MInt dir = 0; dir < noDirs; dir++) {
2952 // Skip if element already has boundary condition for this direction
2953 if(bcIds(elementId, dir) > -1) {
2954 continue;
2955 }
2956
2957 // Skip if no neighbor cell exists
2958 if(!grid().tree().hasNeighbor(cellId, dir)) {
2959 continue;
2960 }
2961
2962 // Skip if neighbor is not an interface cell
2963 const MInt neighborCellId = grid().tree().neighbor(cellId, dir);
2964 if(!isInterface[neighborCellId]) {
2965 continue;
2966 }
2967
2968 // Skip if neighbor element exists
2969 if(needElementForCell(neighborCellId)) {
2970 continue;
2971 }
2972
2973 // Get boundary condition ids for neighbor
2974 auto ids = getBoundaryConditionIds(neighborCellId);
2975
2976 // Check if neighbor cell has boundary condition in the direction of
2977 // current element and if yes, save it for current element
2978 const MInt oppositeDir = 2 * (dir / 2) + 1 - (dir % 2);
2979 if(ids[oppositeDir] > -1) {
2980 bcIds(elementId, dir) = ids[oppositeDir];
2981 }
2982 }
2983 }
2984
2985 // Find cut-off boundaries, i.e., cells that do not have a neighbor while at
2986 // the same time not being intersected by geometry
2987 if(m_useCutOffBoundaries) {
2988 // Reset cut-off boundary counter
2989 fill(m_noCutOffBoundarySurfaces.begin(), m_noCutOffBoundarySurfaces.end(), 0);
2990
2991 // Find and mark cut-off boundaries
2992 for(MInt elementId = 0; elementId < noElements; elementId++) {
2993 const MInt cellId = cellIds[elementId];
2994 for(MInt dir = 0; dir < noDirs; dir++) {
2995 // Skip if no cut-off boundary condition is specified for this direction
2996 if(m_cutOffBoundaryConditionIds[dir] < 0) {
2997 continue;
2998 }
2999
3000 // Skip if element already has boundary condition for this direction
3001 if(bcIds(elementId, dir) > -1) {
3002 continue;
3003 }
3004
3005 // Skip if neighbor cell exists
3006 if(grid().tree().hasNeighbor(cellId, dir)) {
3007 continue;
3008 }
3009
3010 // Skip if neighbor of parent cell exists (will be handled by normal
3011 // h-refinement)
3012 if(grid().tree().parent(cellId) > -1 && grid().tree().hasNeighbor(grid().tree().parent(cellId), dir)) {
3013 continue;
3014 }
3015
3016 // Set boundary condition id to cut-off boundary condition id
3017 bcIds(elementId, dir) = m_cutOffBoundaryConditionIds[dir];
3018
3019 // Count number of cut-off surfaces
3020 m_noCutOffBoundarySurfaces[dir]++;
3021 }
3022 }
3023 }
3024
3025 // Obtain list of unique boundary condition ids
3026 set<MInt> localUniqueBcIds(bcIds.begin(), bcIds.end());
3027
3028 // Remove -1 (represents faces without a boundary condition id)
3029 localUniqueBcIds.erase(-1);
3030
3031 // Exchange all local unique boundary conditions ids and determine all global
3032 // unique boundary condition ids. Each rank will create all boundary
3033 // conditions such that a global communication is possible for e.g.
3034 // reading/writing boundary condition restart data.
3035
3036 // Local number of unique boundary condition ids
3037 MInt noBcIds = localUniqueBcIds.size();
3038
3039 // Counts and offsets for Allgatherv
3040 ScratchSpace<MInt> countsBcIds(noDomains(), FUN_, "countsBcIds");
3041 ScratchSpace<MInt> offsetsBcIds(noDomains(), FUN_, "offsetsBcIds");
3042
3043 // Gather the number of unique boundary condition ids on each domain
3044 MPI_Allgather(&noBcIds, 1, MPI_INT, &countsBcIds[0], 1, MPI_INT, mpiComm(), AT_, "noBcIds", "countsBcIds[0]");
3045
3046 // Compute offsets
3047 offsetsBcIds[0] = 0;
3048 for(MInt dId = 1; dId < noDomains(); dId++) {
3049 offsetsBcIds[dId] = offsetsBcIds[dId - 1] + countsBcIds[dId - 1];
3050 }
3051
3052 // Send buffer
3053 // Avoid dereferencing a zero length array
3054 ScratchSpace<MInt> localBcIds(max(noBcIds, 1), AT_, "localBcIds");
3055 std::copy(localUniqueBcIds.begin(), localUniqueBcIds.end(), &localBcIds[0]);
3056
3057 // Receive buffer
3058 const MInt totalNoBcIds = offsetsBcIds[noDomains() - 1] + countsBcIds[noDomains() - 1];
3059 ScratchSpace<MInt> globalBcIds(totalNoBcIds, FUN_, "globalBcIds");
3060
3061 // Gather and distribute all boundary condition ids from all domains
3062 MPI_Allgatherv(&localBcIds[0], noBcIds, MPI_INT, &globalBcIds[0], &countsBcIds[0], &offsetsBcIds[0], MPI_INT,
3063 mpiComm(), AT_, "localBcIds[0]", "globalBcIds[0]");
3064
3065 // Obtain list of global unique boundary condition ids
3066 set<MInt> uniqueBcIds(globalBcIds.begin(), globalBcIds.end());
3067
3068 // Loop over all boundary conditions and create surfaces, then the boundary
3069 // condition object (so that the result is sorted by bcId)
3070 std::vector<MInt> boundaryConditionIds;
3071 std::vector<std::pair<MInt, MInt>> bcIntervals;
3072 for(const auto& boundaryConditionId : uniqueBcIds) {
3073 const MInt begin = m_noBoundarySurfaces;
3074
3075 for(MInt elementId = 0; elementId < noElements; elementId++) {
3076 for(MInt dir = 0; dir < noDirs; dir++) {
3077 // Skip direction if boundary condition id does not match
3078 if(boundaryConditionId != bcIds(elementId, dir)) {
3079 continue;
3080 }
3081 if(hasSurface(elementId, dir)) {
3082 continue;
3083 }
3084
3085 // Skip if boundary surface creation if periodic connectivity is given in this direction
3086 // Determine periodic direction pDir(+x,+y,+z) based on dir(+-x,+-y,+-z)
3087 // --> pDir = (MInt) dir/2
3088 if(grid().periodicCartesianDir(dir / 2) != 0) {
3089 // Add additional check to avoid unintentional errors (i.e., users must set a periodic
3090 // direction *and* use bcId=0 on all relevant boundaries)
3091 if(boundaryConditionId != 0) {
3092 TERMM(1,
3093 "If you use periodic BCs, you must set the BC id of corresponding boundaries "
3094 "to zero.");
3095 }
3096 continue;
3097 }
3098
3099 initBoundarySurface(elementId, dir);
3100 }
3101 }
3102 const MInt end = m_noBoundarySurfaces;
3103
3104 // Store boundary condition id and the corresponding surface interval
3105 // Boundary conditions are created & initialized after all remaining
3106 // surfaces are created
3107 boundaryConditionIds.push_back(boundaryConditionId);
3108 bcIntervals.emplace_back(begin, end);
3109 }
3110
3111 m_log << "done" << endl;
3112
3114 // 2) Create inner surfaces
3116 m_log << "creating inner surfaces... ";
3117
3118 m_innerSurfacesOffset = m_surfaces.size();
3119 m_noInnerSurfaces = 0;
3120
3121 for(MInt elementId = 0; elementId < noElements; elementId++) {
3122 for(MInt dir = 0; dir < noDirs; dir++) {
3123 if(hasSurface(elementId, dir)) {
3124 continue;
3125 }
3126 initInnerSurface(elementId, dir);
3127 }
3128 }
3129
3130 m_log << "done" << endl;
3131
3133 // 3) Create MPI surfaces
3135 m_log << "creating MPI surfaces... ";
3136
3137 m_mpiSurfacesOffset = m_surfaces.size();
3138 m_noMpiSurfaces = 0;
3139
3140 if(hasMpiExchange()) {
3141 for(MInt elementId = 0; elementId < noElements; elementId++) {
3142 for(MInt dir = 0; dir < noDirs; dir++) {
3143 const MInt cellId = m_elements.cellId(elementId);
3144 const MInt nghbrId = grid().tree().neighbor(cellId, dir);
3145 MBool partLvlAncestorNghbr = false;
3146
3147 // In case of partition level shifts there can be elements at level jumps that have both
3148 // internal and halo cells as neighbors on the higher level. Check if the neighbor cell is a
3149 // candidate for such a case
3150 if(nghbrId > -1 && grid().tree().hasProperty(nghbrId, Cell::IsPartLvlAncestor)
3151 && grid().tree().hasChildren(nghbrId)) {
3152 // Check all childs of the neighbor for a cell and continue process with initMpiSurface()
3153 // below to create any missing h-mpi surfaces (and skip internal childs of the neighbor)
3154 for(MInt child = 0; child < IPOW2(nDim); child++) {
3155 const MInt childId = grid().tree().child(nghbrId, child);
3156 if(childId > -1 && grid().tree().hasProperty(childId, Cell::IsHalo)) {
3157 partLvlAncestorNghbr = true;
3158 }
3159 }
3160 }
3161
3162 if(hasSurface(elementId, dir) && !partLvlAncestorNghbr) {
3163 continue;
3164 }
3165 initMpiSurface(elementId, dir);
3166 }
3167 }
3168 }
3169
3170 m_log << "done" << endl;
3171
3172 // Reset previous boundary condition objects
3173 m_boundaryConditions.clear();
3174
3175 // Create & initialize boundary condition
3176 m_log << "creating and initializing boundary conditions... ";
3177 for(MUint bcId = 0; bcId < boundaryConditionIds.size(); bcId++) {
3178 m_boundaryConditions.emplace_back(make_bc(boundaryConditionIds[bcId]));
3179
3180 const MInt begin = bcIntervals[bcId].first;
3181 const MInt end = bcIntervals[bcId].second;
3182 m_boundaryConditions[bcId]->init(begin, end);
3183 }
3184 m_log << "done" << endl;
3185
3186 // Sanity check: all elements should have surfaces in each direction
3187 m_log << "Sanity check: all elements must have surfaces in each "
3188 "direction... ";
3189 MInt missing = 0;
3190 for(MInt elementId = 0; elementId < noElements; elementId++) {
3191 for(MInt dir = 0; dir < noDirs; dir++) {
3192 if(!hasSurface(elementId, dir)) {
3193 // Write surface information to log
3194 m_log << "\nmissing surface for element " << elementId << " in direction " << dir << " (coordinates: ";
3195 const MInt cellId = m_elements.cellId(elementId);
3196 for(MInt i = 0; i < nDim; i++) {
3197 m_log << grid().tree().coordinate(cellId, i) << " ";
3198 }
3199 m_log << ")" << std::endl;
3200 m_log.flush();
3201
3202 // Count number of missing surfaces for abort message
3203 missing++;
3204 }
3205 }
3206 }
3207
3208 // Abort if there are missing surfaces
3209 if(missing) {
3210 TERMM(1, "There are " + to_string(missing)
3211 + " surfaces missing (check m_log for more details). Did "
3212 "you maybe forget to specify cut-off boundary conditions?");
3213 } else {
3214 m_log << "done" << endl;
3215 }
3216
3217 // Ensure that there are no boundary surfaces if all directions are periodic
3218 if(grid().periodicCartesianDir(0) && grid().periodicCartesianDir(1) && (nDim == 2 || grid().periodicCartesianDir(2))
3219 && m_noBoundarySurfaces > 0) {
3220 TERMM(1, "There are boundary surfaces although every direction has a periodic boundaries. "
3221 "Please check what is wrong here.\n");
3222 }
3223}
3224
3225
3235template <MInt nDim, class SysEqn>
3237 TRACE();
3238 return m_elements.surfaceIds(elementId, dir) > -1;
3239}
3240
3241
3250template <MInt nDim, class SysEqn>
3252 TRACE();
3253
3254 const MInt surfaceId = createSurface(elementId, dir);
3255 m_noBoundarySurfaces++;
3256
3257 return surfaceId;
3258}
3259
3260
3278template <MInt nDim, class SysEqn>
3280 TRACE();
3281
3282 MInt cellId = m_elements.cellId(elementId);
3283
3284 // No surface if there is no neighbor cell on current and parent level
3285 if(!grid().tree().hasNeighbor(cellId, dir)) {
3286 const MInt parentId = grid().tree().parent(cellId);
3287 if(parentId > -1 && grid().tree().hasNeighbor(parentId, dir)) {
3288 // If parent cell exists and it has neighbor in correct direction, use
3289 // parent to determine neighbor cell
3290 cellId = parentId;
3291 } else {
3292 // Otherwise there is no neighbor in the specified direction, so quit
3293 // without creating a surface
3294 return -1;
3295 }
3296 }
3297
3298 const MInt nghbrId = grid().tree().neighbor(cellId, dir);
3299
3300 // If child exists skip since surfaces are created on highest level
3301 if(grid().tree().hasChildren(nghbrId)) {
3302 return -1;
3303 }
3304
3305 // No surface if neighbor is a halo cell
3306 if(grid().tree().hasProperty(nghbrId, Cell::IsHalo)) {
3307 return -1;
3308 }
3309
3310 // No surface if there is no neighbor element
3311 const MInt nghbrElementId = m_elements.getElementByCellId(nghbrId);
3312 if(nghbrElementId == -1) {
3313 return -1;
3314 }
3315
3316 // Create surface
3317 const MInt surfaceId = createSurface(elementId, dir);
3318 m_noInnerSurfaces++;
3319
3320 return surfaceId;
3321}
3322
3323
3337template <MInt nDim, class SysEqn>
3339 TRACE();
3340
3341 MInt cellId = m_elements.cellId(elementId);
3342
3343 // No surface if there is no neighbor cell on current and parent level
3344 if(!grid().tree().hasNeighbor(cellId, dir)) {
3345 const MInt parentId = grid().tree().parent(cellId);
3346 if(parentId > -1 && grid().tree().hasNeighbor(parentId, dir)) {
3347 // If parent cell exists and it has neighbor in correct direction, use
3348 // parent to determine neighbor cell
3349 cellId = parentId;
3350 } else {
3351 // Otherwise there is no neighbor in the specified direction, so quit
3352 // without creating a surface
3353 return -1;
3354 }
3355 }
3356
3357 const MInt nghbrId = grid().tree().neighbor(cellId, dir);
3358
3359 // Surface only with halo cells as neighbor OR
3360 // (in case of partition level shifts) if the neighbor has children (createHMPISurfaces will
3361 // handle/check such cases)
3362 if(!grid().tree().hasProperty(nghbrId, Cell::IsHalo) && !grid().tree().hasChildren(nghbrId)) {
3363 return -1;
3364 }
3365
3366 MInt surfaceId = -1;
3367
3368 // If child exists create multiple MPI surfaces
3369 if(grid().tree().hasChildren(nghbrId)) {
3370 // Create multiple surfaces
3371 surfaceId = createHMPISurfaces(elementId, dir);
3372 } else {
3373 // Create surface
3374 surfaceId = createSurface(elementId, dir);
3375 m_noMpiSurfaces++;
3376 }
3377
3378 return surfaceId;
3379}
3380
3381
3394template <MInt nDim, class SysEqn>
3396 TRACE();
3397
3398 // Set target region to be the minimum and maximum cell coordinates, i.e. of
3399 // the 4 (2D) or 6 (3D) vertices
3400 array<MFloat, 2 * nDim> targetRegion;
3401 for(MInt dir = 0; dir < nDim; dir++) {
3402 targetRegion[dir] = grid().tree().coordinate(cellId, dir) - 0.5 * grid().cellLengthAtCell(cellId);
3403 targetRegion[dir + nDim] = grid().tree().coordinate(cellId, dir) + 0.5 * grid().cellLengthAtCell(cellId);
3404 }
3405
3406 // Get cell coordinates and half length
3407 const MFloat cellHalfLength = 0.5 * grid().cellLengthAtCell(cellId);
3408 array<MFloat, nDim> coordinates;
3409 for(MInt i = 0; i < nDim; i++) {
3410 coordinates[i] = grid().tree().coordinate(cellId, i);
3411 }
3412
3413 // Check if there are elements in the cell
3414 const MBool hasElement = needElementForCell(cellId);
3415
3416 // Get list of intersecting elements
3417 std::vector<MInt> nodeList;
3418 geometry().getIntersectionElements(&targetRegion[0], nodeList, cellHalfLength, &coordinates[0]);
3419
3420 array<MInt, 2 * nDim> bcIds;
3421 bcIds.fill(-1);
3422
3423 // Process each geometric element ('node')
3424 static constexpr MFloat standardBasis[MAX_SPACE_DIMENSIONS][MAX_SPACE_DIMENSIONS] = {
3425 {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}};
3426 for(MInt n = 0; n < (signed)nodeList.size(); n++) {
3427 // Obtain element normal
3428 const GeometryElement<nDim>& elem = geometry().elements[nodeList[n]];
3429 // Obtain geometry vertices
3430 array<MFloat, nDim * nDim> geometryPoints;
3431 elem.getVertices(&geometryPoints[0]);
3432
3433 // Obtain normal
3434 array<MFloat, nDim> normal;
3435 elem.calcNormal(&geometryPoints[0], &normal[0]);
3436
3437 // Check orientation of element normal (axis aligned)
3438 MInt orientation = -1;
3439 for(MInt d = 0; d < nDim; d++) {
3440 const MFloat dot = inner_product(&normal[0], &normal[nDim], standardBasis[d], 0.0);
3441 if(approx(std::fabs(dot), 1.0, MFloatEps)) {
3442 orientation = d;
3443 break;
3444 }
3445 }
3446
3447 // Set boundary id
3448 if(orientation != -1) {
3449 // ... for axis-aligned cases
3450
3451 // Find closest cell face (i.e. determine +ve or -ve placement w.r.t. to the
3452 // cell center)
3453 array<MFloat, nDim> centroid;
3454 elem.calcCentroid(&geometryPoints[0], &centroid[0]);
3455 const MFloat pos = grid().tree().coordinate(cellId, orientation) + 0.5 * grid().cellLengthAtCell(cellId);
3456 const MFloat neg = grid().tree().coordinate(cellId, orientation) - 0.5 * grid().cellLengthAtCell(cellId);
3457 const MInt direction = (std::fabs(neg - centroid[orientation]) < std::fabs(pos - centroid[orientation]))
3458 ? 2 * orientation
3459 : 2 * orientation + 1;
3460
3461 // Abort if boundary condition id for this direction was already set to a
3462 // different value
3463 if(bcIds[direction] > -1 && bcIds[direction] != elem.m_bndCndId) {
3464 stringstream ss;
3465 ss << "Bad cell: " << cellId << ". Cell has two different boundary condition ids (" << bcIds[direction] << ", "
3466 << elem.m_bndCndId << ") in direction " << direction << ". This feature is not yet implemented." << endl;
3467 TERMM(1, ss.str());
3468 }
3469
3470 // Set boundary id
3471 bcIds[direction] = elem.m_bndCndId;
3472
3473 } else {
3474 // ... for non axis-aligned cases
3475
3476 {
3477 // TODO labels:DG,GEOM,toenhance Needs to be adapted for cases where the outer boundary is non axis-aligned
3478 //
3479 // /DESCRIPTION/
3480 //
3481 // Currently, the excerpt of code below only works for the cases where the
3482 // INNER boundary only is non axis-aligned, e.g., an airfoil (2D), or a wing (3D)
3483 // inside the domain. The outer boundary HAS to be axis aligned, e.g., has to be
3484 // a square (2D) or cube (3D). This happens due to the following condition:
3485 //
3486 // direction = (normal[i] < 0) ? 2 * i : 2 * i + 1;
3487 //
3488 // That condition is only capable of assigning the correct direction for INNER
3489 // boundaries with the normal vector pointing outwards the geometry, i.e., pointing
3490 // inside the domain (for outer boundaries this just need to be flipped in order to
3491 // work). However, we currently can't find a proper way to to differentiate if a geometry
3492 // element belongs to the inner or outer boundary.
3493 //
3494 // /POSSIBLE FIX/
3495 //
3496 // A simplistic way of dealing with this issue can be by creating different
3497 // boundary condition IDs for inner and outer boundaries. Then, the code below
3498 // can be modified to check if the current geometry element has a m_bndCndId
3499 // that corresponds to a inner or outer boundary and assign the direction correctly.
3500 // (Remember to also change the geometry.toml files accordingly)
3501 //
3502 for(MInt i = 0; i < nDim; i++) {
3503 if(!approx(normal[i], 0.0, MFloatEps)) {
3504 MInt direction = (normal[i] < 0) ? 2 * i : 2 * i + 1;
3505 // If elements exist in the cell, use the opposite normal direction of geometry face
3506 if(hasElement) {
3507 direction = 2 * (direction / 2) + 1 - (direction % 2);
3508 }
3509
3510 // Abort if h-ref is used along a non axis-aligned boundary
3511 // First check if a neighbor on same level can theoretically exists -> if not a coarser nghbr might exists
3512 array<MFloat, nDim> nghbrCellCoord;
3513 for(MInt d = 0; d < nDim; ++d) {
3514 nghbrCellCoord[d] = grid().tree().coordinate(cellId, d);
3515 }
3516 nghbrCellCoord[i] += (2 * (direction % 2) - 1) * grid().cellLengthAtCell(cellId);
3517 const MBool nghbrIsInside = geometry().pointIsInside2(&nghbrCellCoord[0]);
3518 if((!nghbrIsInside && grid().tree().level(cellId) != getLevelOfDirectNeighborCell(cellId, direction))
3519 || (grid().tree().level(cellId) < getLevelOfDirectNeighborCell(cellId, direction))) {
3520 stringstream ss;
3521 ss << "Bad cell: " << cellId << ". There is h-refinement being used along a non axis-aligned surface in "
3522 << "direction " << direction << ". This feature is not yet implemented." << endl;
3523 TERMM(1, ss.str());
3524 }
3525
3526 // Abort if boundary condition id for this direction was already set to a
3527 // different value
3528 if(bcIds[direction] > -1 && bcIds[direction] != elem.m_bndCndId) {
3529 stringstream ss;
3530 ss << "Bad cell: " << cellId << ". Cell has two different boundary condition ids (" << bcIds[direction]
3531 << ", " << elem.m_bndCndId << ") in direction " << direction
3532 << ". This feature is not yet implemented." << endl;
3533 TERMM(1, ss.str());
3534 }
3535
3536 // Set boundary id
3537 bcIds[direction] = elem.m_bndCndId;
3538 }
3539 }
3540 }
3541
3542 // Candidates for geometry intersection
3543 std::vector<CutCandidate<nDim>> cutCandidates(1);
3544 // Assign cellId as the only candidate
3545 cutCandidates[0].cellId = cellId;
3546
3547 m_geometryIntersection->computeCutPointsFromSTL(cutCandidates);
3548
3549 const MInt noCutPoints = cutCandidates[0].noCutPoints;
3550
3551 // Set target points according to the number of cut points and dimensions (2D or 3D)
3552 vector<MFloat> targetPoints(noCutPoints * nDim);
3553 for(MInt cutPoint = 0; cutPoint < noCutPoints; cutPoint++) {
3554 for(MInt i = 0; i < nDim; i++) {
3555 targetPoints[(cutPoint * nDim) + i] = cutCandidates[0].cutPoints[cutPoint][i];
3556 }
3557 }
3558
3559 // Recalculate the normal for cutPoints
3560 elem.calcNormal(&targetPoints[0], &normal[0]);
3561
3562 // Check the new orientation for non-axis aligned cases
3563 orientation = 0;
3564 MFloat maxNormal = fabs(normal[0]);
3565 for(MInt d = 0; d < nDim; d++) {
3566 if(fabs(normal[d]) > maxNormal) {
3567 orientation = d;
3568 maxNormal = fabs(normal[d]);
3569 }
3570 }
3571
3572 {
3573 // Set boundary ids for faces that are not intersected by geometry
3574 // Find closest cell face (i.e. determine +ve or -ve placement w.r.t. to the cell center)
3575 array<MFloat, nDim> centroid;
3576 elem.calcCentroid(&targetPoints[0], &centroid[0]);
3577 const MFloat pos = grid().tree().coordinate(cellId, orientation) + 0.5 * grid().cellLengthAtCell(cellId);
3578 const MFloat neg = grid().tree().coordinate(cellId, orientation) - 0.5 * grid().cellLengthAtCell(cellId);
3579 MInt direction = (std::fabs(neg - centroid[orientation]) < std::fabs(pos - centroid[orientation]))
3580 ? 2 * orientation
3581 : 2 * orientation + 1;
3582
3583 // Needed when a geometry vertice is inside the cell and the wrong direction is assigned.
3584 // Mirror direction assignment if:
3585 // 1. No elements exist in the cell AND no neighbor exists in the specified direction OR
3586 // 2. Element exists in the cell AND neighbor exists in the speficied direction
3587 if((!hasElement && !grid().tree().hasNeighbor(cellId, direction))
3588 || (hasElement && grid().tree().hasNeighbor(cellId, direction))) {
3589 direction = 2 * (direction / 2) + 1 - (direction % 2);
3590 }
3591 // Abort if h-ref is used along a non axis-aligned boundary
3592 // First check if a neighbor on same level can theoretically exists -> if not a coarser nghbr might exists
3593 array<MFloat, nDim> nghbrCellCoord;
3594 for(MInt d = 0; d < nDim; ++d) {
3595 nghbrCellCoord[d] = grid().tree().coordinate(cellId, d);
3596 }
3597 nghbrCellCoord[orientation] += (2 * (direction % 2) - 1) * grid().cellLengthAtCell(cellId);
3598 const MBool nghbrIsInside = geometry().pointIsInside2(&nghbrCellCoord[0]);
3599 if((!nghbrIsInside && grid().tree().level(cellId) != getLevelOfDirectNeighborCell(cellId, direction))
3600 || (grid().tree().level(cellId) < getLevelOfDirectNeighborCell(cellId, direction))) {
3601 stringstream ss;
3602 ss << "Bad cell: " << cellId << ". There is h-refinement being used along a non axis-aligned surface "
3603 << "in direction " << direction << ". This feature is not yet implemented." << endl;
3604 TERMM(1, ss.str());
3605 }
3606 // Abort if boundary condition id for this direction was already set to a
3607 // different value
3608 if(bcIds[direction] > -1 && bcIds[direction] != elem.m_bndCndId) {
3609 stringstream ss;
3610 ss << "Bad cell: " << cellId << ". Cell has two different boundary condition ids (" << bcIds[direction]
3611 << ", " << elem.m_bndCndId << ") in direction " << direction << ". This feature is not yet implemented."
3612 << endl;
3613 TERMM(1, ss.str());
3614 }
3615
3616 // Set boundary id
3617 bcIds[direction] = elem.m_bndCndId;
3618 }
3619 }
3620 }
3621
3622 return bcIds;
3623}
3624
3625
3626/*
3627 * \brief Creates multiple surfaces between an element and its refined neighbor
3628 * elements for an inter-domain (MPI) boundary.
3629 *
3630 * \author Sven Berger
3631 * \date April 2015
3632 *
3633 * \details This method assumes that elementId is always a valid element, i.e.
3634 * that it is at an MPI boundary and that it is the coarser element.
3635 *
3636 * \param[in] elementId The element id of the coarse element.
3637 *
3638 * \param[in] dir Direction id of neighbor elements relative to the main element
3639 * (i.e. 0 = -x, 1 = +x, 2 = -y etc.)
3640 *
3641 * \return The surface id of the first newly created surface.
3642 */
3643template <MInt nDim, class SysEqn>
3645 // TRACE();
3646
3647 // Determine neighbor element
3648 const MInt cellId = m_elements.cellId(elementId);
3649
3650 // Get neighbor cells from child level
3651 vector<MInt> nghbrIds;
3652 const MInt parentNghbrId = grid().tree().neighbor(cellId, dir);
3653
3654 // Arrays give child id of child level neighbors for a neighbor cell in a
3655 // given direction
3656 static constexpr MInt dirNghbrs[6][4] = {
3657 {1, 3, 5, 7}, // -x direction
3658 {0, 2, 4, 6}, // +x direction
3659 {2, 3, 6, 7}, // -y direction
3660 {0, 1, 4, 5}, // +y direction
3661 {4, 5, 6, 7}, // -z direction
3662 {0, 1, 2, 3} // +z direction
3663 };
3664
3665 MInt noNonHaloChilds = 0;
3666 // Store every existing child level neighbor element
3667 for(MInt i = 0; i < 2 * (nDim - 1); i++) {
3668 if(grid().tree().hasChild(parentNghbrId, dirNghbrs[dir][i])) {
3669 // Check if child is an internal cell (partition level shift), skip
3670 const MInt childId = grid().tree().child(parentNghbrId, dirNghbrs[dir][i]);
3671 if(!grid().tree().hasProperty(childId, Cell::IsHalo)) {
3672 ASSERT(grid().tree().hasProperty(parentNghbrId, Cell::IsPartLvlAncestor),
3673 "this case is only valid with a neighboring partition level ancestor cell");
3674 noNonHaloChilds++;
3675 continue;
3676 }
3677 nghbrIds.push_back(grid().tree().child(parentNghbrId, dirNghbrs[dir][i]));
3678 }
3679 }
3680
3681 // Partition level shift: only neighboring non-halo childs, nothing to do
3682 if(noNonHaloChilds == 2 * (nDim - 1)) {
3683 return -1;
3684 }
3685
3686 if(nghbrIds.empty()) {
3687 stringstream errorMessage;
3688 errorMessage << "Error: child cells of neighboring halo cell not found. "
3689 "Try setting 'newCreateWindowCellsF = 0'."
3690 << endl;
3691 TERMM(1, errorMessage.str());
3692 }
3693
3694 // Create a surface for each child level neighbor element
3695 MInt firstSurfaceId = -1;
3696 for(const auto& nghbrId : nghbrIds) {
3697 // First, create normal surface
3698 const MInt srfcId = createSurface(elementId, dir, nghbrId);
3699
3700 // Store first created surface for the return value
3701 if(firstSurfaceId == -1) {
3702 firstSurfaceId = srfcId;
3703 }
3704
3705 // Then, add surface id of new surface to corresponding h-element
3706 for(MInt hElementId = 0; hElementId < m_helements.size(); hElementId++) {
3707 // Find h-element of the current element
3708 if(m_helements.elementId(hElementId) == elementId) {
3709 // Add surface id at first unused location
3710 for(MInt hSurfId = 0; hSurfId < 2 * (nDim - 1); hSurfId++) {
3711 if(m_helements.hrefSurfaceIds(hElementId, dir, hSurfId) == -1) {
3712 m_helements.hrefSurfaceIds(hElementId, dir, hSurfId) = srfcId;
3713 break;
3714 }
3715 }
3716 break;
3717 }
3718 }
3719
3720 // Set h-refinement specific variables
3721 m_surfaces.fineCellId(srfcId) = nghbrId;
3722
3723 // Increase counter
3724 m_noMpiSurfaces++;
3725 }
3726
3727 return firstSurfaceId;
3728}
3729
3730
3731/*
3732 * \brief Creates a surface between two neighboring elements.
3733 *
3734 * \author Michael Schlottke
3735 * \date October 2012
3736 *
3737 * \details This method assumes that elementId is always a valid element.
3738 * nghbrId might be a non-existing element (i.e. -1).
3739 *
3740 * \param[in] elementId The element id of the "main" adjacent element. This must
3741 * be a valid element!
3742 * \param[in] dir Direction id of neighbor element relative to the main element
3743 * (i.e. 0 = -x, 1 = +x, 2 = -y etc.)
3744 * \param[in] nghbrId CellId of the neighbor cell (needs to be set *only* for
3745 * halo cells)
3746 *
3747 * \return The surface id of the newly created surface.
3748 */
3749template <MInt nDim, class SysEqn>
3751 // TRACE();
3752
3753 // Determine neighbor element
3754 const MInt cellId = m_elements.cellId(elementId);
3755 const MInt parentId = grid().tree().parent(cellId);
3756
3757 // Get neighbor from current or parent cell (or leave id as -1 if no neighbor
3758 // found)
3759 if(nghbrId == -1) {
3760 nghbrId = grid().tree().neighbor(cellId, dir);
3761 if(nghbrId == -1 && parentId > -1 && grid().tree().hasNeighbor(parentId, dir)) {
3762 nghbrId = grid().tree().neighbor(parentId, dir);
3763 }
3764 }
3765 const MInt nghbrElementId = m_elements.getElementByCellId(nghbrId);
3766
3767 // Determine new surface id and create the surface in collector
3768 const MInt srfcId = m_surfaces.size();
3769 m_surfaces.append();
3770
3771 // Calculate opposite direction
3772 const MInt oppositeDir = 2 * (dir / 2) + 1 - (dir % 2);
3773
3774 // Calculate and set the surface orientation (0: x, 1: y, 2: z)
3775 const MInt orientation = dir / 2;
3776 m_surfaces.orientation(srfcId) = orientation;
3777
3778 // Calculate and set the neighboring element ids (element in -ve direction is
3779 // first)
3780 const MInt nghbrSideId = dir % 2;
3781 const MInt elementSideId = (nghbrSideId + 1) % 2;
3782 m_surfaces.nghbrElementIds(srfcId, nghbrSideId) = nghbrElementId;
3783 m_surfaces.nghbrElementIds(srfcId, elementSideId) = elementId;
3784
3785 // Set surfaceId for element
3786 m_elements.surfaceIds(elementId, dir) = srfcId;
3787
3788 // Set the surfaceId for the neighbor element if it is not already set
3789 if(nghbrElementId > -1 && m_elements.surfaceIds(nghbrElementId, oppositeDir) == -1) {
3790 m_elements.surfaceIds(nghbrElementId, oppositeDir) = srfcId;
3791 }
3792
3793 // Get levels of element and neighbor element (if existing)
3794 const MInt level = getLevelByElementId(elementId);
3795 MInt nghbrLvl = -1;
3796 if(nghbrElementId > -1) {
3797 nghbrLvl = getLevelByElementId(nghbrElementId);
3798 }
3799
3800 // Reset fine cell id
3801 m_surfaces.fineCellId(srfcId) = -1;
3802
3803 // Set h-elements if a neighbor with a lower level exists
3804 if(nghbrLvl != -1 && level > nghbrLvl) {
3805 // Iterate over h-elements
3806 for(MInt hElementId = 0; hElementId < m_helements.size(); hElementId++) {
3807 // Find h-element for neighbor element
3808 if(m_helements.elementId(hElementId) == nghbrElementId) {
3809 m_surfaces.fineCellId(srfcId) = cellId;
3810 // Add surface id at first unused location
3811 for(MInt hSurfId = 0; hSurfId < 2 * (nDim - 1); hSurfId++) {
3812 if(m_helements.hrefSurfaceIds(hElementId, oppositeDir, hSurfId) == -1) {
3813 m_helements.hrefSurfaceIds(hElementId, oppositeDir, hSurfId) = srfcId;
3814 break;
3815 }
3816 }
3817 break;
3818 }
3819 }
3820 }
3821
3822 // Set polynomial degree to maximum of both elements' polynomial degrees
3823 // Set number of nodes 1D to maximum of both elements' number of nodes 1D
3824 if(nghbrElementId > -1) {
3825 m_surfaces.polyDeg(srfcId) = max(m_elements.polyDeg(elementId), m_elements.polyDeg(nghbrElementId));
3826 m_surfaces.noNodes1D(srfcId) = max(m_elements.noNodes1D(elementId), m_elements.noNodes1D(nghbrElementId));
3827 } else {
3828 m_surfaces.polyDeg(srfcId) = m_elements.polyDeg(elementId);
3829 m_surfaces.noNodes1D(srfcId) = m_elements.noNodes1D(elementId);
3830 }
3831
3832
3833 // Reset level in case there is a neighbor cell
3834 if(nghbrId > -1) {
3835 nghbrLvl = grid().tree().level(nghbrId);
3836 }
3837
3838 // Compute surface coordinates by using the higher level cell
3839 if(level > nghbrLvl) {
3840 const MFloat cellLength = grid().cellLengthAtCell(cellId);
3841 m_surfaces.coords(srfcId, orientation) =
3842 grid().tree().coordinate(cellId, orientation) + (static_cast<MFloat>(nghbrSideId) - F1B2) * cellLength;
3843 for(MInt i = 0; i < nDim; i++) {
3844 if(i != orientation) {
3845 m_surfaces.coords(srfcId, i) = grid().tree().coordinate(cellId, i);
3846 }
3847 }
3848 } else {
3849 const MFloat cellLength = grid().cellLengthAtCell(nghbrId);
3850 m_surfaces.coords(srfcId, orientation) =
3851 grid().tree().coordinate(nghbrId, orientation) + (static_cast<MFloat>(1 - nghbrSideId) - F1B2) * cellLength;
3852 for(MInt i = 0; i < nDim; i++) {
3853 if(i != orientation) {
3854 m_surfaces.coords(srfcId, i) = grid().tree().coordinate(nghbrId, i);
3855 }
3856 }
3857 }
3858
3859 // Set internal side id
3860 const MInt elementIdL = m_surfaces.nghbrElementIds(srfcId, 0);
3861 const MInt elementIdR = m_surfaces.nghbrElementIds(srfcId, 1);
3862 // If a valid nghbrCell exists only one side -> mark this side
3863 // otherwise keep -1
3864 m_surfaces.internalSideId(srfcId) = -1;
3865 if(elementIdL == -1) {
3866 m_surfaces.internalSideId(srfcId) = 1;
3867 }
3868 if(elementIdR == -1) {
3869 m_surfaces.internalSideId(srfcId) = 0;
3870 }
3871
3872 // Calculate node coordinates
3873 calcSurfaceNodeCoordinates(srfcId);
3874
3875 // Determine & set global surface id
3876 const MLong globalCellId = grid().tree().globalId(m_elements.cellId(elementId));
3877 if(nghbrId == -1 || nghbrLvl == -1) {
3878 // If no neighbor cell exists, take the global id from the cell
3879 m_surfaces.globalId(srfcId) = 2 * nDim * globalCellId + dir;
3880 } else {
3881 // If neighbor cell exists, take the higher level cell or if both have the
3882 // same level take the cell with the lower global cell id
3883 const MLong globalNghbrId = grid().tree().globalId(nghbrId);
3884 if(level > nghbrLvl || (globalCellId < globalNghbrId && level == nghbrLvl)) {
3885 m_surfaces.globalId(srfcId) = 2 * nDim * globalCellId + dir;
3886 } else {
3887 m_surfaces.globalId(srfcId) = 2 * nDim * globalNghbrId + oppositeDir;
3888 }
3889 }
3890
3891 return srfcId;
3892}
3893
3894
3895/*
3896 * \brief Calc coordinates of the nodes on the surface.
3897 *
3898 * \author Sven Berger
3899 * \date Februar 2015
3900 *
3901 * \param[in] srfcId The surface id of the surface for which the node
3902 * coordinates are calculated.
3903 */
3904template <MInt nDim, class SysEqn>
3906 const MInt internalSide = (m_surfaces.internalSideId(srfcId) <= 0) ? 0 : 1;
3907 const MInt elementId = m_surfaces.nghbrElementIds(srfcId, internalSide);
3908 const MInt cellId = m_elements.cellId(elementId);
3909
3910 // Compute surface node coordinates
3911 const MInt polyDeg = m_surfaces.polyDeg(srfcId);
3912 const MInt noNodes1D = m_surfaces.noNodes1D(srfcId);
3913 const MInt noNodes1D3 = (nDim == 3) ? noNodes1D : 1;
3914 const MFloat length = grid().cellLengthAtCell(cellId);
3915 MFloatTensor nodeCoordinates(&m_surfaces.nodeCoords(srfcId), noNodes1D, noNodes1D3, nDim);
3916
3917 // Iterate over all nodes and set the coordinates
3918 // Node coordinates = surface center
3919 // + (1/2 element length * normalized ([-1,1]) node coordinate)
3920 switch(m_surfaces.orientation(srfcId)) {
3921 case 0: // x-direction
3922 for(MInt j = 0; j < noNodes1D; j++) {
3923 for(MInt k = 0; k < noNodes1D3; k++) {
3924 nodeCoordinates(j, k, 0) = m_surfaces.coords(srfcId, 0);
3925 nodeCoordinates(j, k, 1) =
3926 m_surfaces.coords(srfcId, 1) + F1B2 * length * m_interpolation[polyDeg][noNodes1D].m_nodes[j];
3927 IF_CONSTEXPR(nDim == 3) {
3928 nodeCoordinates(j, k, 2) =
3929 m_surfaces.coords(srfcId, 2) + F1B2 * length * m_interpolation[polyDeg][noNodes1D].m_nodes[k];
3930 }
3931 }
3932 }
3933 break;
3934
3935 case 1: // y-direction
3936 for(MInt i = 0; i < noNodes1D; i++) {
3937 for(MInt k = 0; k < noNodes1D3; k++) {
3938 nodeCoordinates(i, k, 0) =
3939 m_surfaces.coords(srfcId, 0) + F1B2 * length * m_interpolation[polyDeg][noNodes1D].m_nodes[i];
3940 nodeCoordinates(i, k, 1) = m_surfaces.coords(srfcId, 1);
3941 IF_CONSTEXPR(nDim == 3) {
3942 nodeCoordinates(i, k, 2) =
3943 m_surfaces.coords(srfcId, 2) + F1B2 * length * m_interpolation[polyDeg][noNodes1D].m_nodes[k];
3944 }
3945 }
3946 }
3947 break;
3948
3949 case 2: // z-direction
3950 for(MInt i = 0; i < noNodes1D; i++) {
3951 for(MInt j = 0; j < noNodes1D3; j++) {
3952 nodeCoordinates(i, j, 0) =
3953 m_surfaces.coords(srfcId, 0) + F1B2 * length * m_interpolation[polyDeg][noNodes1D].m_nodes[i];
3954 nodeCoordinates(i, j, 1) =
3955 m_surfaces.coords(srfcId, 1) + F1B2 * length * m_interpolation[polyDeg][noNodes1D].m_nodes[j];
3956 IF_CONSTEXPR(nDim == 3) { nodeCoordinates(i, j, 2) = m_surfaces.coords(srfcId, 2); }
3957 }
3958 }
3959 break;
3960
3961 default:
3962 mTerm(1, AT_, "Bad orientation");
3963 }
3964}
3965
3966
3967/* Determines if MPI exchange has to take place, i.e. if computation is in parallel or a periodic
3968 * BC is specified If there is another condition than periodicity where an MPI exchange shall
3969 * occur, please modify
3970 */
3971template <MInt nDim, class SysEqn>
3973 TRACE();
3974
3975 // If InitMpiExchange has not been performed yet: Initialize it, if the grid has neighbor
3976 // domains. Else if InitMpiExchange has been performed: m_noExchangeNghbrDomains describes
3977 // number of neighbor domains, with which actually data has to be exchanged. Look also in
3978 // initMpiExchange().
3979 const MBool neighbors = m_isInitMpiExchange ? m_noExchangeNghbrDomains > 0 : grid().noNeighborDomains() > 0;
3980
3981 // Return true if executed on more than one domain *or* if there exist neighbor domains (the
3982 // latter may occur in case of periodicity)
3983 return (noDomains() > 1 || neighbors);
3984}
3985
3986
3987/*
3988 * \brief Initialize data arrays that are needed for MPI communication, i.e.
3989 * buffers and persistent MPI requests.
3990 *
3991 * \author Michael Schlottke
3992 * \date July 2013
3993 *
3994 */
3995template <MInt nDim, class SysEqn>
3997 TRACE();
3998
3999 m_log << "Initialize MPI exchange... ";
4000
4001 // Create map domain ids to exchange neighbor domain ids
4002 map<MInt, MInt> exchangeNghbrDomainMap;
4003
4004 // Reset number of exchange neighbor domains (i.e. domains with which actual
4005 // data needs to be exchanged, as opposed to neighbor domains, with which just
4006 // halo/window cells are shared).
4007 m_noExchangeNghbrDomains = 0;
4008
4009 // Initialize resize containers to max. number of exchange domains
4010 m_exchangeNghbrDomains.resize(grid().noNeighborDomains());
4011 m_mpiSurfaces.resize(grid().noNeighborDomains());
4012
4013 // Fill MPI surfaces container with surfaces that need to be exchanged with
4014 // the respective domain
4015 const MInt begin = m_mpiSurfacesOffset;
4016 const MInt end = m_mpiSurfacesOffset + m_noMpiSurfaces;
4017 for(MInt srfcId = begin; srfcId < end; srfcId++) {
4018 const MInt internalSideId = m_surfaces.internalSideId(srfcId);
4019 const MInt elementId = m_surfaces.nghbrElementIds(srfcId, internalSideId);
4020 const MInt internalCellId = m_elements.cellId(elementId);
4021 const MInt nghbrCellDir = 2 * m_surfaces.orientation(srfcId) + 1 - internalSideId;
4022
4023 // Get halo cell
4024 MInt haloCellId = grid().tree().neighbor(internalCellId, nghbrCellDir);
4025 if(haloCellId == -1) {
4026 // If halo cell does not exist, get parent-level cell
4027 haloCellId = grid().tree().neighbor(grid().tree().parent(internalCellId), nghbrCellDir);
4028 }
4029
4030 // Get child-level halo cells if halo cell has children
4031 if(grid().tree().hasChildren(haloCellId)) {
4032 haloCellId = m_surfaces.fineCellId(srfcId);
4033 }
4034
4035 // Determine domain id
4036 MInt exchangeNghbrDomainId = -1;
4037 for(MInt i = 0; i < noDomains() + 1; i++) {
4038 if(grid().domainOffset(i) > grid().tree().globalId(haloCellId)) {
4039 exchangeNghbrDomainId = i - 1;
4040 break;
4041 }
4042 }
4043 ASSERT(exchangeNghbrDomainId > -1, "Could not find domain id!");
4044
4045 // Add new exchange neighbor domain if not yet existing
4046 if(exchangeNghbrDomainMap.count(exchangeNghbrDomainId) == 0) {
4047 m_exchangeNghbrDomains[m_noExchangeNghbrDomains] = exchangeNghbrDomainId;
4048 exchangeNghbrDomainMap[exchangeNghbrDomainId] = m_noExchangeNghbrDomains;
4049 m_noExchangeNghbrDomains++;
4050 }
4051
4052 // Add surface to container
4053 m_mpiSurfaces[exchangeNghbrDomainMap[exchangeNghbrDomainId]].push_back(srfcId);
4054 }
4055
4056 // Reset size of containers to actual size
4057 m_exchangeNghbrDomains.resize(m_noExchangeNghbrDomains);
4058 m_mpiSurfaces.resize(m_noExchangeNghbrDomains);
4059
4060 // Sort the MPI surfaces container according to the global surface id
4061 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
4062 sort(m_mpiSurfaces[i].begin(), m_mpiSurfaces[i].end(),
4063 [this](const MInt a, const MInt b) { return (m_surfaces.globalId(a) < m_surfaces.globalId(b)); });
4064 }
4065
4066 // Build a container for receiving periodic interfaces
4067 m_mpiRecvSurfaces.resize(m_noExchangeNghbrDomains);
4068 m_mpiRecvSurfaces = m_mpiSurfaces;
4069
4070 // Modification for periodic boundary conditions. This is required since in case of periodic
4071 // boundary conditions with periodicity on the same MPI rank, two surfaces exist with the same
4072 // global surface id.
4073 if(grid().periodicCartesianDir(0) || grid().periodicCartesianDir(1)
4074 || (nDim == 3 && grid().periodicCartesianDir(2))) {
4075 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
4076 // If a global surface id exists twice within one domain, change its order for receiving
4077 for(vector<MInt>::size_type j = 0; j < m_mpiSurfaces[i].size() - 1; j++) {
4078 if(m_surfaces.globalId(m_mpiSurfaces[i][j]) == m_surfaces.globalId(m_mpiSurfaces[i][j + 1])) {
4079 swap(m_mpiRecvSurfaces[i][j], m_mpiRecvSurfaces[i][j + 1]);
4080 }
4081 }
4082 }
4083 }
4084
4085
4086 // TODO labels:DG,toremove Remove this debugging output once multi-solver simulations are properly tested
4087 // TODO labels:DG add test to check if mpi surfaces match on exchange domains!
4088 // stringstream ss;
4089 // ss << solverId() << " " << domainId() << " " << m_noExchangeNghbrDomains << " exchange neighb:";
4090 // for (MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
4091 // ss << " " << m_exchangeNghbrDomains[i];
4092 //}
4093 // ss << endl;
4094 // for (MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
4095 // ss << solverId() << " " << domainId() << " " << m_mpiSurfaces[i].size()
4096 // << " MPI surfaces for domain " << m_exchangeNghbrDomains[i] << ":";
4097 // for (MInt j = 0; j < static_cast<MInt>(m_mpiSurfaces[i].size()); j++) {
4098 // ss << " " << m_surfaces.globalId(m_mpiSurfaces[i][j]);
4099 // }
4100 // ss << endl;
4101 //}
4102 // ss << endl;
4103 // cout << ss.str() << endl;
4104 // m_log << ss.str() << endl;
4105
4106 // IMPORTANT:
4107 // If anything in the following loops is changed, please check if
4108 // updateNodeVariables() needs to be changed as well, since it contains more
4109 // or less the same code.
4110
4111#ifdef DG_USE_MPI_BUFFERS
4112 // Resize send/receive buffers
4113 m_sendBuffers.resize(m_noExchangeNghbrDomains);
4114 m_recvBuffers.resize(m_noExchangeNghbrDomains);
4115 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
4116 MInt size = 0;
4117 for(vector<MInt>::size_type j = 0; j < m_mpiSurfaces[i].size(); j++) {
4118 // Use max. noNodes to account for p-refined surfaces
4119 const MInt noNodes1D = m_maxNoNodes1D;
4120 const MInt noNodesXD = ipow(noNodes1D, nDim - 1);
4121 const MInt dataBlockSize = noNodesXD * SysEqn::noVars();
4122 size += dataBlockSize;
4123 }
4124
4125 m_sendBuffers[i].resize(size);
4126 m_recvBuffers[i].resize(size);
4127 }
4128#endif
4129#ifdef DG_USE_MPI_DERIVED_TYPES
4130#error Does not work anymore - need to fix for p-refinement!
4131 // Create and commit MPI derived datatypes for send/recv operations
4132 m_sendTypes.resize(m_noExchangeNghbrDomains);
4133 m_recvTypes.resize(m_noExchangeNghbrDomains);
4134 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
4135 // Note: This method uses non-MAIA types for interfacing with an external
4136 // library
4137 // Count specifies the number of solvers in the derived data type
4138 const MInt count = m_mpiSurfaces[i].size();
4139
4140 // If count is zero, skip this domain (since there is no data to exchange)
4141 // and send the data type to a null type. This is used later to determine
4142 // that no data needs to be exchanged.
4143 if(count == 0) {
4144 m_sendTypes[i] = MPI_DATATYPE_NULL;
4145 m_recvTypes[i] = MPI_DATATYPE_NULL;
4146 continue;
4147 }
4148
4149 vector<MInt> lengths(count);
4150 vector<MPI_Aint> displacementsSend(count);
4151 vector<MPI_Aint> displacementsRecv(count);
4152
4153 // Fill lengths and displacements vectors
4154 for(vector<MInt>::size_type j = 0; j < m_mpiSurfaces[i].size(); j++) {
4155 const MInt srfcId = m_mpiSurfaces[i][j];
4156 // Set solver length
4157 const MInt noNodes1D = m_surfaces.noNodes1D(srfcId);
4158 const MInt noNodesXD = ipow(noNodes1D, nDim - 1);
4159 const MInt dataBlockSize = noNodesXD * SysEqn::noVars();
4160 lengths[j] = dataBlockSize;
4161
4162 // Set displacements
4163 const MInt internalSideId = m_surfaces.internalSideId(srfcId);
4164 MPI_Get_address(m_surfaces.variables(srfcId, internalSideId), &displacementsSend[j], AT_);
4165 MPI_Get_address(m_surfaces.variables(srfcId, 1 - internalSideId), &displacementsRecv[j], AT_);
4166 }
4167
4168 // Create MPI data types (hindexed since we're using absolute addresses for
4169 // the displacement)
4170 MPI_Type_create_hindexed(count, &lengths[0], &displacementsSend[0], type_traits<MFloat>::mpiType(), &m_sendTypes[i],
4171 AT_);
4172 MPI_Type_create_hindexed(count, &lengths[0], &displacementsRecv[0], type_traits<MFloat>::mpiType(), &m_recvTypes[i],
4173 AT_);
4174
4175 // Commit data types
4176 MPI_Type_commit(&m_sendTypes[i], AT_);
4177 MPI_Type_commit(&m_recvTypes[i], AT_);
4178 }
4179#endif
4180
4181 // Create persistent requests
4182 m_sendRequests.resize(m_noExchangeNghbrDomains);
4183 m_recvRequests.resize(m_noExchangeNghbrDomains);
4184 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
4185#ifdef DG_USE_MPI_BUFFERS
4186 MPI_Send_init(&m_sendBuffers[i][0], m_sendBuffers[i].size(), type_traits<MFloat>::mpiType(),
4187 m_exchangeNghbrDomains[i], domainId(), mpiComm(), &m_sendRequests[i], AT_, "m_sendBuffers[i][0]");
4188 MPI_Recv_init(&m_recvBuffers[i][0], m_recvBuffers[i].size(), type_traits<MFloat>::mpiType(),
4189 m_exchangeNghbrDomains[i], m_exchangeNghbrDomains[i], mpiComm(), &m_recvRequests[i], AT_,
4190 "m_recvBuffers[i][0]");
4191#endif
4192#ifdef DG_USE_MPI_DERIVED_TYPES
4193 MPI_Send_init(MPI_BOTTOM, 1, m_sendTypes[i], m_exchangeNghbrDomains[i], domainId(), mpiComm(), &m_sendRequests[i],
4194 AT_, "MPI_BOTTOM");
4195 MPI_Recv_init(MPI_BOTTOM, 1, m_recvTypes[i], m_exchangeNghbrDomains[i], m_exchangeNghbrDomains[i], mpiComm(),
4196 &m_recvRequests[i], AT_, "MPI_BOTTOM");
4197#endif
4198 }
4199
4200 m_isInitMpiExchange = true;
4201 m_log << "done" << endl;
4202}
4203
4204
4205/*
4206 * \brief Calculate and - if necessary - exchange all statistical information
4207 * about the simulation itself (e.g. total number of active cells,
4208 * elements, surfaces etc.)
4209 *
4210 * \author Michael Schlottke
4211 * \date July 2013
4212 *
4213 */
4214template <MInt nDim, class SysEqn>
4216 TRACE();
4217
4218 m_log << "Initializing simulation statistics... ";
4219
4220 // Set or determine local statistics
4221
4222 // Minimum & maximum grid refinement level
4223 m_statLocalMinLevel = grid().minLevel();
4224 m_statLocalMaxLevel = grid().maxLevel();
4225
4226 // Total cell counts
4227 m_statLocalNoCells = grid().noCells();
4228 m_statLocalNoInternalCells = grid().noInternalCells();
4229 m_statLocalNoHaloCells = grid().noCells() - grid().noInternalCells();
4230 m_statLocalMaxNoCells = grid().raw().treeb().capacity();
4231
4232 // Total element counts
4233 m_statLocalNoElements = m_elements.size();
4234 m_statLocalNoHElements = m_helements.size();
4235
4236 // Total surface counts
4237 m_statLocalNoSurfaces = m_surfaces.size();
4238 m_statLocalNoBoundarySurfaces = m_noBoundarySurfaces;
4239 m_statLocalNoInnerSurfaces = m_noInnerSurfaces;
4240 m_statLocalNoMpiSurfaces = m_noMpiSurfaces;
4241 m_statLocalMaxNoSurfaces = m_maxNoSurfaces;
4242
4243 // Active cell/DOF count
4244 m_statLocalNoActiveCells = m_elements.size();
4245 m_statLocalNoActiveDOFs =
4246 accumulate(&m_elements.noNodes1D(0), &m_elements.noNodes1D(0) + m_elements.size(), 0,
4247 [](const MInt result, const MInt noNodes1D) { return result + ipow(noNodes1D, nDim); });
4248
4249 // Count active DOF per polynomial degree (if there are multiple polyDegs)
4250 const MInt noPolyDegs = m_maxPolyDeg - m_minPolyDeg + 1;
4251 if(noPolyDegs > 1) {
4252 m_statLocalNoActiveDOFsPolyDeg.assign(noPolyDegs, 0);
4253 const MInt noElements = m_elements.size();
4254 for(MInt elementId = 0; elementId < noElements; elementId++) {
4255 const MInt polyDeg = m_elements.polyDeg(elementId);
4256 const MInt noDOFs = m_elements.noNodesXD(elementId);
4257 m_statLocalNoActiveDOFsPolyDeg[polyDeg - m_minPolyDeg] += noDOFs;
4258 }
4259 }
4260
4261
4262 // Minimum & maximum polynomial degrees
4263 m_statLocalMinPolyDeg = *min_element(&m_elements.polyDeg(0), &m_elements.polyDeg(0) + m_elements.size());
4264 m_statLocalMaxPolyDeg = *max_element(&m_elements.polyDeg(0), &m_elements.polyDeg(0) + m_elements.size());
4265
4266 // Form sum of polynomial degress for average calculation
4267 m_statLocalPolyDegSum = accumulate(&m_elements.polyDeg(0), &m_elements.polyDeg(0) + m_elements.size(), 0);
4268
4269 // Number of h- and p-refined surfaces
4270 m_statLocalNoHrefSurfs = count_if(&m_surfaces.fineCellId(0), &m_surfaces.fineCellId(0) + m_surfaces.size(),
4271 [](const MInt id) { return id != -1; });
4272 m_statLocalNoPrefSurfs = 0;
4273 // Only counted for inner surfaces
4274 // TODO labels:DG Also count for MPI surfaces
4275 for(MInt srfcId = m_innerSurfacesOffset; srfcId < m_innerSurfacesOffset + m_noInnerSurfaces; srfcId++) {
4276 const MInt elementIdL = m_surfaces.nghbrElementIds(srfcId, 0);
4277 const MInt elementIdR = m_surfaces.nghbrElementIds(srfcId, 1);
4278 if(m_elements.polyDeg(elementIdL) != m_elements.polyDeg(elementIdR)) {
4279 m_statLocalNoPrefSurfs++;
4280 }
4281 }
4282
4283 // Determine global statistics
4284
4285 // Pack buffer
4286 MLong buffer[26];
4287 // Min
4288 buffer[0] = m_statLocalMinLevel;
4289 buffer[1] = m_statLocalMinPolyDeg;
4290 // Max
4291 buffer[2] = m_statLocalMaxLevel;
4292 buffer[3] = m_statLocalMaxPolyDeg;
4293 // Sum
4294 buffer[4] = m_statLocalNoCells;
4295 buffer[5] = m_statLocalNoInternalCells;
4296 buffer[6] = m_statLocalNoHaloCells;
4297 buffer[7] = m_statLocalMaxNoCells;
4298 buffer[8] = m_statLocalNoElements;
4299 buffer[9] = m_statLocalNoSurfaces;
4300 buffer[10] = m_statLocalNoBoundarySurfaces;
4301 buffer[11] = m_statLocalNoInnerSurfaces;
4302 buffer[12] = m_statLocalNoMpiSurfaces;
4303 buffer[13] = m_statLocalMaxNoSurfaces;
4304 buffer[14] = m_statLocalNoActiveCells;
4305 buffer[15] = m_statLocalNoActiveDOFs;
4306 buffer[16] = m_statLocalPolyDegSum;
4307 buffer[17] = m_statLocalNoHrefSurfs;
4308 buffer[18] = m_statLocalNoPrefSurfs;
4309 buffer[19] = m_statLocalNoHElements;
4310 buffer[20] = m_noCutOffBoundarySurfaces[0];
4311 buffer[21] = m_noCutOffBoundarySurfaces[1];
4312 buffer[22] = m_noCutOffBoundarySurfaces[2];
4313 buffer[23] = m_noCutOffBoundarySurfaces[3];
4314 IF_CONSTEXPR(nDim == 3) {
4315 buffer[24] = m_noCutOffBoundarySurfaces[4];
4316 buffer[25] = m_noCutOffBoundarySurfaces[5];
4317 }
4318 else {
4319 buffer[24] = 0;
4320 buffer[25] = 0;
4321 }
4322
4323 RECORD_TIMER_START(m_timers[Timers::Accumulated]);
4324 RECORD_TIMER_START(m_timers[Timers::MPI]);
4325 RECORD_TIMER_START(m_timers[Timers::MPIComm]);
4326
4327 // Exchange statistics
4328 // Operation: min
4329 MPI_Allreduce(MPI_IN_PLACE, &buffer[0], 2, MPI_INT, MPI_MIN, mpiComm(), AT_, "MPI_IN_PLACE", "buffer[0]");
4330 // Operation: max
4331 MPI_Allreduce(MPI_IN_PLACE, &buffer[2], 2, MPI_INT, MPI_MAX, mpiComm(), AT_, "MPI_IN_PLACE", "buffer[2]");
4332 // Operation: sum
4333 MPI_Allreduce(MPI_IN_PLACE, &buffer[4], 22, maia::type_traits<MLong>::mpiType(), MPI_SUM, mpiComm(), AT_,
4334 "MPI_IN_PLACE", "buffer[4]");
4335
4336 RECORD_TIMER_STOP(m_timers[Timers::MPIComm]);
4337 RECORD_TIMER_STOP(m_timers[Timers::MPI]);
4338 RECORD_TIMER_STOP(m_timers[Timers::Accumulated]);
4339
4340 // Unpack buffer
4341 m_statGlobalMinLevel = buffer[0];
4342 m_statGlobalMinPolyDeg = buffer[1];
4343 m_statGlobalMaxLevel = buffer[2];
4344 m_statGlobalMaxPolyDeg = buffer[3];
4345 m_statGlobalNoCells = buffer[4];
4346 m_statGlobalNoInternalCells = buffer[5];
4347 m_statGlobalNoHaloCells = buffer[6];
4348 m_statGlobalMaxNoCells = buffer[7];
4349 m_statGlobalNoElements = buffer[8];
4350 m_statGlobalNoSurfaces = buffer[9];
4351 m_statGlobalNoBoundarySurfaces = buffer[10];
4352 m_statGlobalNoInnerSurfaces = buffer[11];
4353 m_statGlobalNoMpiSurfaces = buffer[12];
4354 m_statGlobalMaxNoSurfaces = buffer[13];
4355 m_statGlobalNoActiveCells = buffer[14];
4356 m_statGlobalNoActiveDOFs = buffer[15];
4357 m_statGlobalPolyDegSum = buffer[16];
4358 m_statGlobalNoHrefSurfs = buffer[17];
4359 m_statGlobalNoPrefSurfs = buffer[18];
4360 m_statGlobalNoHElements = buffer[19];
4361 m_statGlobalNoCutOffBoundarySurfaces[0] = buffer[20];
4362 m_statGlobalNoCutOffBoundarySurfaces[1] = buffer[21];
4363 m_statGlobalNoCutOffBoundarySurfaces[2] = buffer[22];
4364 m_statGlobalNoCutOffBoundarySurfaces[3] = buffer[23];
4365 m_statGlobalNoCutOffBoundarySurfaces[4] = buffer[24];
4366 m_statGlobalNoCutOffBoundarySurfaces[5] = buffer[25];
4367
4368 m_log << "done" << endl;
4369}
4370
4371
4383template <MInt nDim, class SysEqn>
4385 TRACE();
4386
4387 // Nothing to do if solver is not active
4388 if(!isActive()) {
4389 RECORD_TIMER_STOP(m_timers[Timers::Run]);
4390 return;
4391 }
4392
4393 RECORD_TIMER_START(m_timers[Timers::CleanUp]);
4394
4395 // Abort if solver not initialized
4396 if(!m_isInitSolver) {
4397 TERMM(1, "Solver was not initialized.");
4398 }
4399
4400 // Free persistent MPI requests
4401 if(hasMpiExchange()) {
4402 finalizeMpiExchange();
4403 }
4404
4405 RECORD_TIMER_STOP(m_timers[Timers::CleanUp]);
4406 RECORD_TIMER_STOP(m_timers[Timers::Run]);
4407}
4408
4409
4417template <MInt nDim, class SysEqn>
4419 TRACE();
4420
4421 // Complete already started communication in split-MPI mode
4422 if(g_splitMpiComm && m_mpiSendRequestsOpen) {
4423 RECORD_TIMER_START(m_timers[Timers::MainLoop]);
4424 RECORD_TIMER_START(m_timers[Timers::RungeKuttaStep]);
4425 RECORD_TIMER_START(m_timers[Timers::TimeDeriv]);
4426 finishMpiSurfaceExchange();
4427 RECORD_TIMER_STOP(m_timers[Timers::TimeDeriv]);
4428 RECORD_TIMER_STOP(m_timers[Timers::RungeKuttaStep]);
4429 RECORD_TIMER_STOP(m_timers[Timers::MainLoop]);
4430
4431 // Wait for send requests to finish
4432 MPI_Waitall(m_noExchangeNghbrDomains, &m_sendRequests[0], MPI_STATUSES_IGNORE, AT_);
4433 m_mpiSendRequestsOpen = false;
4434 }
4435
4436 // Cancel opened receive requests
4437 if(m_mpiRecvRequestsOpen) {
4438 std::vector<MBool> waitForCancel(m_noExchangeNghbrDomains, false);
4439 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
4440 if(m_recvRequests[i] != MPI_REQUEST_NULL) {
4441 MPI_Cancel(&m_recvRequests[i], AT_);
4442 waitForCancel[i] = true;
4443 }
4444 }
4445 // Wait for all requests until they are canceled
4446 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
4447 if(waitForCancel[i]) {
4448 MPI_Wait(&m_recvRequests[i], MPI_STATUS_IGNORE, AT_);
4449 }
4450 }
4451 m_mpiRecvRequestsOpen = false;
4452 }
4453}
4454
4455
4456/*
4457 * \brief Free resources that were allocated in initMpiExchange().
4458 *
4459 * \author Michael Schlottke
4460 * \date July 2013
4461 *
4462 */
4463template <MInt nDim, class SysEqn>
4465 TRACE();
4466
4467 if(m_noExchangeNghbrDomains > 0) {
4468 // Wait for open send requests to finish
4469 if(m_mpiSendRequestsOpen) {
4470 MPI_Waitall(m_noExchangeNghbrDomains, &m_sendRequests[0], MPI_STATUSES_IGNORE, AT_);
4471 }
4472
4473 // Cancel and free requests if they are non-null
4474 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
4475 if(m_sendRequests[i] != MPI_REQUEST_NULL) {
4476 // Note: canceling send requests might cause MPI errors depending on the MPI
4477 // implementation https://github.com/mpi-forum/mpi-forum-historic/issues/479
4478 /* MPI_Cancel(&m_sendRequests[i], AT_); */
4479 MPI_Request_free(&m_sendRequests[i], AT_);
4480 }
4481
4482 if(m_recvRequests[i] != MPI_REQUEST_NULL) {
4483 // Cancel opened receive request that do not have a matching send initiated yet
4484 if(m_mpiRecvRequestsOpen) {
4485 MPI_Cancel(&m_recvRequests[i], AT_);
4486 }
4487 MPI_Request_free(&m_recvRequests[i], AT_);
4488 }
4489 }
4490 }
4491
4492 m_mpiSendRequestsOpen = false;
4493 m_mpiRecvRequestsOpen = false;
4494
4495#ifdef DG_USE_MPI_DERIVED_TYPES
4496 // Free derived data types
4497 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
4498 MPI_Type_free(&m_sendTypes[i], AT_);
4499 MPI_Type_free(&m_recvTypes[i], AT_);
4500 }
4501#endif
4502}
4503
4504
4511template <MInt nDim, class SysEqn>
4513 TRACE();
4514 RECORD_TIMER_START(m_timers[Timers::InitialCondition]);
4515
4516 m_log << "Applying initial conditions... ";
4517
4518 const MFloat time = m_startTime;
4519
4520 // First apply initial conditions from coupling
4521 // Note: changed with unified run loop, if you need to overwrite an initial condition set by the
4522 // coupling initialCondition() needs to be called again from the coupler!
4523
4524 // Iterate over all elements
4525 const MInt noElements = m_elements.size();
4526 for(MInt elementId = 0; elementId < noElements; elementId++) {
4527 const MInt noNodes1D = m_elements.noNodes1D(elementId);
4528 const MInt noNodes1D3 = (nDim == 3) ? noNodes1D : 1;
4529 // Set node vars to minimum 1 to avoid errors in Tensor
4530 // TODO labels:DG,totest Check if this is really sensible
4531 MFloatTensor nodeVars(&m_elements.nodeVars(elementId), noNodes1D, noNodes1D, noNodes1D3,
4532 max(SysEqn::noNodeVars(), 1));
4533 MFloatTensor u(&m_elements.variables(elementId), noNodes1D, noNodes1D, noNodes1D3, SysEqn::noVars());
4534 MFloatTensor x(&m_elements.nodeCoordinates(elementId), noNodes1D, noNodes1D, noNodes1D3, nDim);
4535 // Loop over all nodes and apply initial condition at all integration points
4536 for(MInt i = 0; i < noNodes1D; i++) {
4537 for(MInt j = 0; j < noNodes1D; j++) {
4538 for(MInt k = 0; k < noNodes1D3; k++) {
4539 // Apply initial condition
4540 m_sysEqn.calcInitialCondition(time, &x(i, j, k, 0), &nodeVars(i, j, k, 0), &u(i, j, k, 0));
4541 }
4542 }
4543 }
4544 }
4545
4546 m_log << "done" << endl;
4547
4548 RECORD_TIMER_STOP(m_timers[Timers::InitialCondition]);
4549}
4550
4551
4564template <MInt nDim, class SysEqn>
4566 TRACE();
4567
4568 m_log << "Update node variable data on surfaces... ";
4569
4570 const MInt* const polyDegs = &m_elements.polyDeg(0);
4571 const MInt* const noNodes = &m_elements.noNodes1D(0);
4572 const MInt* const surfaceIds = &m_elements.surfaceIds(0, 0);
4573 const MInt noElements = m_elements.size();
4574
4576 // Prolong to surfaces
4578
4579 // IMPORTANT:
4580 // If anything in this part of the method is changed, please check if
4581 // prolongToSurfaces() needs to be changed as well, since it contains more
4582 // or less the same code.
4583 using namespace dg::interpolation;
4584
4585 if(m_dgIntegrationMethod == DG_INTEGRATE_GAUSS) {
4586#ifdef _OPENMP
4587#pragma omp parallel for
4588#endif
4589 // Loop over all elements in given range
4590 for(MInt elementId = 0; elementId < noElements; elementId++) {
4591 const MInt surfaceIdOffset = elementId * 2 * nDim;
4592 const MInt polyDeg = polyDegs[elementId];
4593 const MInt noNodes1D = noNodes[elementId];
4594 const DgInterpolation& interp = m_interpolation[polyDeg][noNodes1D];
4595
4596 // Extrapolate the solution to each surface on the faces
4597 for(MInt dir = 0; dir < 2 * nDim; dir++) {
4598 const MInt srfcId = surfaceIds[surfaceIdOffset + dir];
4599 const MInt side = 1 - dir % 2;
4600
4601 MFloat* src = &m_elements.nodeVars(elementId);
4602 MFloat* dest = &m_surfaces.nodeVars(srfcId, side);
4603 prolongToFaceGauss<nDim, SysEqn::noNodeVars()>(src, dir, noNodes1D, &interp.m_LFace[0][0],
4604 &interp.m_LFace[1][0], dest);
4605 }
4606 }
4607 } else if(m_dgIntegrationMethod == DG_INTEGRATE_GAUSS_LOBATTO) {
4608#ifdef _OPENMP
4609#pragma omp parallel for
4610#endif
4611 // Loop over all elements in given range
4612 for(MInt elementId = 0; elementId < noElements; elementId++) {
4613 const MInt surfaceIdOffset = elementId * 2 * nDim;
4614 const MInt noNodes1D = noNodes[elementId];
4615
4616 // Extrapolate the solution to each surface on the faces
4617 for(MInt dir = 0; dir < 2 * nDim; dir++) {
4618 const MInt srfcId = surfaceIds[surfaceIdOffset + dir];
4619 const MInt side = 1 - dir % 2;
4620
4621 MFloat* src = &m_elements.nodeVars(elementId);
4622 MFloat* dest = &m_surfaces.nodeVars(srfcId, side);
4623 prolongToFaceGaussLobatto<nDim, SysEqn::noNodeVars()>(src, dir, noNodes1D, dest);
4624 }
4625 }
4626 }
4627
4629 // Forward projection for hp-refined surfaces
4631
4632 // IMPORTANT:
4633 // If anything in this part of the method is changed, please check if
4634 // applyForwardProjection() needs to be changed as well, since it contains
4635 // more or less the same code.
4636
4637 const MInt noVars = SysEqn::noNodeVars();
4638 const MInt maxNoNodes1D = m_maxNoNodes1D;
4639 const MInt maxNoNodes1D3 = (nDim == 3) ? maxNoNodes1D : 1;
4640 const MInt noHElements = m_helements.size();
4641 const MInt noDirs = 2 * nDim;
4642 const MInt noSurfs = 2 * (nDim - 1);
4643
4644 // Copy prolong results to h-refined surfaces (the prolong step stored its
4645 // results only in the first refined surface)
4646 if(noHElements > 0) {
4647#ifdef _OPENMP
4648#pragma omp parallel for
4649#endif
4650 for(MInt hElementId = 0; hElementId < noHElements; hElementId++) {
4651 const MInt coarseElementId = m_helements.elementId(hElementId);
4652 for(MInt dir = 0; dir < noDirs; dir++) {
4653 const MInt coarseSrfcId = m_elements.surfaceIds(coarseElementId, dir);
4654 for(MInt pos = 0; pos < noSurfs; pos++) {
4655 const MInt hSrfcId = m_helements.hrefSurfaceIds(hElementId, dir, pos);
4656 const MInt side = 1 - dir % 2;
4657
4658 // Skip if this surface does not exist
4659 if(hSrfcId == -1) {
4660 continue;
4661 }
4662
4663 // Copy values of prolong step to all remaining refined surfaces
4664 if(coarseSrfcId != hSrfcId) {
4665 const MInt noNodes1D = noNodes[coarseElementId];
4666 const MInt noNodes1D3 = (nDim == 3) ? noNodes1D : 1;
4667 const MInt noNodesXD = noNodes1D * noNodes1D3;
4668
4669 // Copy nodeVars from prolong step to surface
4670 copy_n(&m_surfaces.nodeVars(coarseSrfcId, side),
4671 noNodesXD * SysEqn::noNodeVars(),
4672 &m_surfaces.nodeVars(hSrfcId, side));
4673 }
4674 }
4675 }
4676 }
4677 }
4678
4679 // Apply mortar projection
4680 MFloatTensor projected(maxNoNodes1D, maxNoNodes1D3, noVars);
4681
4682 // Apply projection to h- (and possibly p-)refined surfaces
4683 if(noHElements > 0) {
4684#ifdef _OPENMP
4685#pragma omp parallel for firstprivate(projected)
4686#endif
4687 for(MInt hElementId = 0; hElementId < noHElements; hElementId++) {
4688 for(MInt dir = 0; dir < noDirs; dir++) {
4689 for(MInt pos = 0; pos < noSurfs; pos++) {
4690 const MInt hSrfcId = m_helements.hrefSurfaceIds(hElementId, dir, pos);
4691 const MInt side = 1 - dir % 2;
4692
4693 // Skip if this surface does not exist
4694 if(hSrfcId == -1) {
4695 continue;
4696 }
4697
4698 const MInt surfaceNoNodes1D = m_surfaces.noNodes1D(hSrfcId);
4699 const MInt surfaceNoNodes1D3 = (nDim == 3) ? surfaceNoNodes1D : 1;
4700 const MInt size = surfaceNoNodes1D * surfaceNoNodes1D3 * noVars;
4701
4702 // Project the coarse side to the surface
4703 calcMortarProjection<dg::mortar::forward, SysEqn::noNodeVars()>(
4704 hSrfcId, dir, &m_surfaces.nodeVars(hSrfcId, side), &projected[0], m_elements, m_surfaces);
4705
4706 // Copy results of projection back to surface
4707 copy_n(&projected[0], size, &m_surfaces.nodeVars(hSrfcId, side));
4708 }
4709 }
4710 }
4711 }
4712
4713 // Apply projection to pure p-refined surfaces
4714#ifdef _OPENMP
4715#pragma omp parallel for firstprivate(projected)
4716#endif
4717 for(MInt elementId = 0; elementId < noElements; elementId++) {
4718 const MInt surfaceIdOffset = elementId * 2 * nDim;
4719
4720 for(MInt dir = 0; dir < 2 * nDim; dir++) {
4721 // Define auxiliary variables for better readability
4722 const MInt srfcId = surfaceIds[surfaceIdOffset + dir];
4723
4724 // Skip boundary surfaces as they are *always* conforming
4725 if(srfcId < m_innerSurfacesOffset) {
4726 continue;
4727 }
4728
4729 const MInt surfacePolyDeg = m_surfaces.polyDeg(srfcId);
4730 const MInt elementPolyDeg = m_elements.polyDeg(elementId);
4731 const MInt side = 1 - dir % 2;
4732 const MInt surfaceNoNodes1D = m_surfaces.noNodes1D(srfcId);
4733 const MInt surfaceNoNodes1D3 = (nDim == 3) ? surfaceNoNodes1D : 1;
4734 const MInt size = surfaceNoNodes1D * surfaceNoNodes1D3 * noVars;
4735
4736 // Skip h-refined surfaces since they have been already projected
4737 if(m_surfaces.fineCellId(srfcId) != -1 && m_surfaces.fineCellId(srfcId) != m_elements.cellId(elementId)) {
4738 continue;
4739 }
4740
4741 // Calculate forward projection for lower polyDeg elements
4742 if(surfacePolyDeg > elementPolyDeg) {
4743 // Calculate forward projection
4744 calcMortarProjection<dg::mortar::forward, SysEqn::noNodeVars()>(srfcId, dir, &m_surfaces.nodeVars(srfcId, side),
4745 &projected[0], m_elements, m_surfaces);
4746
4747 // Copy results of projection back to surface
4748 copy_n(&projected[0], size, &m_surfaces.nodeVars(srfcId, side));
4749 }
4750 }
4751 }
4752
4754 // MPI exchange
4756
4757 // Exit here if there is nothing to communicate
4758 if(!hasMpiExchange()) {
4759 return;
4760 }
4761
4762 // IMPORTANT:
4763 // If anything in this part of the method is changed, please check if
4764 // initMpiExchange()/startMpiSurfaceExchange()/finishMpiSurfaceExchange()
4765 // needs to be changed as well, since they contain more or less the same code.
4766
4767 // Set up buffers and requests
4768 vector<vector<MFloat>> sendBuffers(m_noExchangeNghbrDomains);
4769 vector<vector<MFloat>> recvBuffers(m_noExchangeNghbrDomains);
4770 const MInt dataBlockSize = ipow(m_maxNoNodes1D, nDim - 1) * SysEqn::noNodeVars();
4771 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
4772 const MInt size = m_mpiSurfaces[i].size() * dataBlockSize;
4773 sendBuffers[i].resize(size);
4774 recvBuffers[i].resize(size);
4775 }
4776 ScratchSpace<MPI_Request> sendRequests(m_noExchangeNghbrDomains, AT_, "sendRequests");
4777 fill(sendRequests.begin(), sendRequests.end(), MPI_REQUEST_NULL);
4778 ScratchSpace<MPI_Request> recvRequests(m_noExchangeNghbrDomains, AT_, "recvRequests");
4779 fill(recvRequests.begin(), recvRequests.end(), MPI_REQUEST_NULL);
4780
4781 // Start receiving
4782 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
4783 MPI_Irecv(&recvBuffers[i][0], recvBuffers[i].size(), type_traits<MFloat>::mpiType(), m_exchangeNghbrDomains[i],
4784 m_exchangeNghbrDomains[i], mpiComm(), &recvRequests[i], AT_, "recvBuffers[i][0]");
4785 }
4786
4787 // Fill send buffers
4788 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
4789 MInt size = 0;
4790 for(vector<MInt>::size_type j = 0; j < m_mpiSurfaces[i].size(); j++) {
4791 const MInt srfcId = m_mpiSurfaces[i][j];
4792 const MInt sideId = m_surfaces.internalSideId(srfcId);
4793
4794 // Copy nodeVars data
4795 const MFloat* data = &m_surfaces.nodeVars(srfcId, sideId);
4796 copy_n(data, dataBlockSize, &sendBuffers[i][size]);
4797 size += dataBlockSize;
4798 }
4799 ASSERT(size == static_cast<MInt>(sendBuffers[i].size()), "Data size does not match buffer size.");
4800 }
4801
4802 // Start sending
4803 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
4804 MPI_Isend(&sendBuffers[i][0], sendBuffers[i].size(), type_traits<MFloat>::mpiType(), m_exchangeNghbrDomains[i],
4805 domainId(), mpiComm(), &sendRequests[i], AT_, "sendBuffers[i][0]");
4806 }
4807
4808 // Finish receiving
4809 MPI_Waitall(m_noExchangeNghbrDomains, &recvRequests[0], MPI_STATUSES_IGNORE, AT_);
4810
4811 // Unpack receive buffers
4812 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
4813 MInt size = 0;
4814 for(vector<MInt>::size_type j = 0; j < m_mpiSurfaces[i].size(); j++) {
4815 const MInt srfcId = m_mpiSurfaces[i][j];
4816 const MInt sideId = 1 - m_surfaces.internalSideId(srfcId);
4817
4818 // Copy nodeVars data
4819 MFloat* data = &m_surfaces.nodeVars(srfcId, sideId);
4820 std::copy_n(&recvBuffers[i][size], dataBlockSize, data);
4821 size += dataBlockSize;
4822 }
4823 ASSERT(size == static_cast<MInt>(recvBuffers[i].size()), "Data size does not match buffer size.");
4824 }
4825
4826 // Finish sending
4827 MPI_Waitall(m_noExchangeNghbrDomains, &sendRequests[0], MPI_STATUSES_IGNORE, AT_);
4828
4829 m_log << "done" << endl;
4830}
4831
4832
4844template <MInt nDim, class SysEqn>
4846 // check if this feature is activated properly in the .toml file
4847 MBool nodeVarsExtension = false;
4848 if(Context::propertyExists("nodeVarsExtension", m_solverId)) {
4849 nodeVarsExtension = Context::getSolverProperty<MBool>("nodeVarsExtension", m_solverId, AT_, &nodeVarsExtension);
4850 } else {
4851 const MBool extensionSetImplicit = Context::propertyExists("meanExtendDirections", m_solverId)
4852 || Context::propertyExists("meanExtendOffsets", m_solverId)
4853 || Context::propertyExists("meanExtendLimits", m_solverId);
4854 nodeVarsExtension = extensionSetImplicit;
4855 }
4856
4857 // return if neither flag(explicit) nor parameters(implicit) are set
4858 if(!nodeVarsExtension) {
4859 return;
4860 }
4861
4862 // read in directions and offsets (which specify planes)
4863 const MInt noExtendDirs = Context::propertyLength("meanExtendDirections", m_solverId);
4864 const MInt noExtendOffsets = Context::propertyLength("meanExtendOffsets", m_solverId);
4865 const MInt noExtendLimits = Context::propertyLength("meanExtendLimits", m_solverId);
4866 if(noExtendOffsets != noExtendDirs || noExtendLimits != noExtendDirs) {
4867 TERMM(1, "ERROR: # of specified directions (" + to_string(noExtendDirs) + "), # of offsets ("
4868 + to_string(noExtendOffsets) + "), and # of limits (" + to_string(noExtendLimits) + ") do not match!");
4869 }
4870
4871 std::vector<MInt> meanExtendDirections(noExtendDirs);
4872 std::vector<MFloat> meanExtendOffsets(noExtendDirs);
4873 std::vector<MFloat> meanExtendLimits(noExtendDirs);
4874
4875 for(MInt i = 0; i < noExtendDirs; i++) {
4876 meanExtendDirections[i] = Context::getSolverProperty<MInt>("meanExtendDirections", m_solverId, AT_, i);
4877 meanExtendOffsets[i] = Context::getSolverProperty<MFloat>("meanExtendOffsets", m_solverId, AT_, i);
4878 meanExtendLimits[i] = Context::getSolverProperty<MFloat>("meanExtendLimits", m_solverId, AT_, i);
4879 }
4880
4881 // trigger extension for each direction/offset-pair
4882 for(MInt i = 0; i < noExtendDirs; i++) {
4883 if(meanExtendDirections[i] >= 0 && meanExtendDirections[i] < 2 * nDim) {
4884 extendNodeVariablesSingleDirection(meanExtendDirections[i], meanExtendOffsets[i], meanExtendLimits[i]);
4885 }
4886 }
4887}
4888
4889
4903template <MInt nDim, class SysEqn>
4905 const MFloat extendOffset,
4906 const MFloat extendLimit) {
4907 m_log << "Propagating mean values in direction " << extendDir << " from offset " << extendOffset << "..."
4908 << std::endl;
4909 // set up and pre-compute grid and extension variables
4910 const MInt noElements = m_elements.size();
4911 const MInt* const noNodes1D = &m_elements.noNodes1D(0);
4912 const MInt extendSide = extendDir % 2;
4913 const MInt extendDimension = (extendDir - extendSide) / 2;
4914 const MInt donorDir = 2 * extendDimension + (1 - extendSide);
4915 const MInt noHElements = m_helements.size();
4916 const MInt noSurfs = 2 * (nDim - 1);
4917
4918 // precompute map from surfaceId to surfaceId of h-refined child surfaces
4919 // for the surface, which is shared between the coarse element and one of the fine elements,
4920 // this gives for every position the surface of the child surface of that position. for child
4921 // surfaces, it gives number of surfaces+1, because they should not acces anything surface
4922 // related. for unrefined surfaces, all entries are -1
4923 // surfIdForward has the recv side's surfaces, because forward mortar projection is used there
4924 // to
4925 // get from coarse(1 element) to fine (multiple child elements)
4926 // surfIdReverse has the donor side's surfaces, because reverse mortar projection is used there
4927 // to
4928 // get from fine (multiple child elements) to coarse(1 element)
4929 MIntTensor hForwardSurfaceId(m_surfaces.size(), noSurfs);
4930 MIntTensor hReverseSurfaceId(m_surfaces.size(), noSurfs);
4931 hForwardSurfaceId.set(-1);
4932 hReverseSurfaceId.set(-1);
4933 for(MInt hElemId = 0; hElemId < noHElements; hElemId++) {
4934 const MInt elemId = m_helements.elementId(hElemId);
4935 // get global surface id for this surface
4936 const MInt surfIdForward = m_elements.surfaceIds(elemId, extendDir);
4937 const MInt surfIdReverse = m_elements.surfaceIds(elemId, donorDir);
4938 for(MInt pos = 0; pos < noSurfs; pos++) {
4939 hForwardSurfaceId(surfIdForward, pos) = m_helements.hrefSurfaceIds(hElemId, extendDir, pos);
4940 const MInt hReverseSurfaceIdFine = m_helements.hrefSurfaceIds(hElemId, donorDir, pos);
4941 hReverseSurfaceId(surfIdReverse, pos) = hReverseSurfaceIdFine;
4942 // this prevents access on a valid surface, but marks children surfs as refined
4943 if(hReverseSurfaceId(surfIdReverse, pos) != surfIdReverse && hReverseSurfaceIdFine > 0) {
4944 hReverseSurfaceId(hReverseSurfaceIdFine, 0) = m_surfaces.size() + 1;
4945 }
4946 }
4947 }
4948 // this is needed for hrefinement on mpisurfaces; for the lack of booltensor, these are ints
4949 MIntTensor surfaceHasRecvd(m_surfaces.size());
4950 surfaceHasRecvd.set(0.0);
4951 // mark those elements as updated, which are on the plane specified by the offset
4952 std::vector<MBool> elementUpdated(noElements, false);
4953 std::vector<MBool> elementOutsideLimit(noElements, false);
4954 std::vector<MInt> mpiSurfaceSendSize(m_surfaces.size(), 0);
4955 for(MInt elemId = 0; elemId < noElements; elemId++) {
4956 // assume here: all elements have either only old nodeVars or only new nodeVars
4957 const MFloat pSideCoordinate =
4958 m_surfaces.coords(m_elements.surfaceIds(elemId, extendDimension * 2 + 1), extendDimension);
4959 const MFloat mSideCoordinate =
4960 m_surfaces.coords(m_elements.surfaceIds(elemId, extendDimension * 2), extendDimension);
4961 elementUpdated[elemId] = (pSideCoordinate >= extendOffset && mSideCoordinate <= extendOffset);
4962 // mark elements that are outside the extend limit coordinates
4963 if((!extendSide && pSideCoordinate <= extendLimit && mSideCoordinate <= extendLimit)
4964 || (extendSide && pSideCoordinate >= extendLimit && mSideCoordinate >= extendLimit)) {
4965 elementOutsideLimit[elemId] = true;
4966 }
4967 // mark mpi surfaces with initially updated NodeVars as ready to send data to neighbor domain
4968 if(elementUpdated[elemId]) {
4969 const MInt sId = m_elements.surfaceIds(elemId, extendDir);
4970 if(sId >= m_mpiSurfacesOffset) {
4971 const MInt buffSize = ipow(noNodes1D[elemId], nDim - 1) * SysEqn::noNodeVars();
4972 mpiSurfaceSendSize[sId] = buffSize;
4973 if(hForwardSurfaceId(sId, 0) > -1) {
4974 for(MInt pos = 0; pos < noSurfs; pos++) {
4975 const MInt hElemId = m_surfaces.nghbrElementIds(sId, extendSide);
4976 mpiSurfaceSendSize[m_elements.surfaceIds(hElemId, extendDir)] = buffSize;
4977 }
4978 }
4979 }
4980 }
4981 }
4982
4983 for(MInt extendIterator = 0; extendIterator < 4 * noDomains() + 1; extendIterator++) {
4984 if(extendIterator == 4 * noDomains()) {
4985 // terminate because strict upper bound for number if iterations is reached
4986 for(MInt elemId = 0; elemId < noElements; elemId++) {
4987 if(elementUpdated[elemId] == 0) {
4988 m_log << "r" << domainId() << " e" << elemId << " not updated" << std::endl;
4989 }
4990 }
4991 TERMM(1, "Maximum amount of extension iterations reached.");
4992 }
4993 for(MInt elementCounter = 0; elementCounter < noElements; elementCounter++) {
4994 MBool noMoreUpdates = true;
4995 for(MInt elemId = 0; elemId < noElements; elemId++) {
4996 // update neighbor element in extend direction, skipping elements that
4997 // have no neighbor in extend direction to update
4998 const MInt srfcId = m_elements.surfaceIds(elemId, extendDir);
4999 if(srfcId < m_innerSurfacesOffset) {
5000 continue;
5001 }
5002 if(srfcId < m_mpiSurfacesOffset) {
5003 const MInt updateeElementId = m_surfaces.nghbrElementIds(srfcId, extendSide);
5004 if(elementUpdated[elemId] && !(elementUpdated[updateeElementId])
5005 && !(elementOutsideLimit[updateeElementId])) {
5006 updateNodeVariablesSingleElement(updateeElementId, extendDir, hForwardSurfaceId, hReverseSurfaceId,
5007 elementUpdated, mpiSurfaceSendSize, noMoreUpdates);
5008 noMoreUpdates = false;
5009 }
5010 }
5011 }
5012 if(noMoreUpdates) {
5013 break;
5014 }
5015 }
5016
5017 // MPI communication of surfaces updated in this iteration:
5018 MInt extendDomainId;
5019 MPI_Comm_rank(mpiComm(), &extendDomainId);
5020 // exchange information about whether any domain has updated nodeVars to communicate
5021 MInt thisRankIsDone = 1;
5022 for(MInt a = 0; a < m_surfaces.size(); a++) {
5023 if(mpiSurfaceSendSize[a] > 0) {
5024 thisRankIsDone = 0;
5025 }
5026 }
5027 MInt allRanksAreDone;
5028 MPI_Allreduce(&thisRankIsDone, &allRanksAreDone, 1, MPI_INT, MPI_MIN, mpiComm(), AT_, "thisRankIsDone",
5029 "allRanksAreDone");
5030 if(allRanksAreDone) {
5031 break;
5032 }
5033
5034 // exchange information on how much data to recv from each neighbor domain
5035 // allocate send and recv metadata buffer and fill send metadata buffer
5036 std::vector<std::vector<MInt>> nghbrSendSizes(m_noExchangeNghbrDomains);
5037 std::vector<std::vector<MInt>> nghbrRecvSizes(m_noExchangeNghbrDomains);
5038
5039 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
5040 nghbrSendSizes[i].resize(m_mpiSurfaces[i].size());
5041 nghbrRecvSizes[i].resize(m_mpiSurfaces[i].size());
5042 for(vector<MInt>::size_type j = 0; j < m_mpiSurfaces[i].size(); j++) {
5043 nghbrSendSizes[i][j] = mpiSurfaceSendSize[m_mpiSurfaces[i][j]];
5044 mpiSurfaceSendSize[m_mpiSurfaces[i][j]] = 0;
5045 }
5046 }
5047
5048 // Exchange metadata buffers.
5049 std::vector<MPI_Request> metaDataRecvRequests(m_noExchangeNghbrDomains);
5050 std::vector<MPI_Request> metaDataSendRequests(m_noExchangeNghbrDomains);
5051 for(MInt nghbrIndex = 0; nghbrIndex < m_noExchangeNghbrDomains; nghbrIndex++) {
5052 MPI_Irecv(&nghbrRecvSizes[nghbrIndex][0], nghbrRecvSizes[nghbrIndex].size(), type_traits<MInt>::mpiType(),
5053 m_exchangeNghbrDomains[nghbrIndex], 0, mpiComm(), &metaDataRecvRequests[nghbrIndex], AT_,
5054 "nghbrRecvSizes[nghbrIndex][0]");
5055 MPI_Isend(&nghbrSendSizes[nghbrIndex][0], nghbrSendSizes[nghbrIndex].size(), type_traits<MInt>::mpiType(),
5056 m_exchangeNghbrDomains[nghbrIndex], 0, mpiComm(), &metaDataSendRequests[nghbrIndex], AT_,
5057 "nghbrSendSizes[nghbrIndex][0]");
5058 }
5059 MPI_Waitall(m_noExchangeNghbrDomains, &metaDataRecvRequests[0], MPI_STATUS_IGNORE, AT_);
5060
5061 // Exchange actual nodevars to neighbordomains.
5062 // Set up buffers.
5063 vector<vector<MFloat>> nodeVarsSendBuffers(m_noExchangeNghbrDomains);
5064 vector<vector<MFloat>> nodeVarsRecvBuffers(m_noExchangeNghbrDomains);
5065 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
5066 const MInt sendSize = accumulate(nghbrSendSizes[i].begin(), nghbrSendSizes[i].end(), 0);
5067 const MInt recvSize = accumulate(nghbrRecvSizes[i].begin(), nghbrRecvSizes[i].end(), 0);
5068 nodeVarsSendBuffers[i].resize(sendSize);
5069 nodeVarsRecvBuffers[i].resize(recvSize);
5070
5071 // Fill sendbuffer.
5072 MInt size = 0;
5073 for(vector<MInt>::size_type j = 0; j < m_mpiSurfaces[i].size(); j++) {
5074 if(nghbrSendSizes[i][j] < 1) {
5075 continue;
5076 }
5077 const MInt srfcId = m_mpiSurfaces[i][j];
5078 // The side of the surface from which the nodevars are copied should
5079 // not matter, as both sides have to contain updated nodeVars in an
5080 // appropriate basis.
5081 const MFloat* data = &m_surfaces.nodeVars(srfcId, 1 - extendSide);
5082 copy(data, data + nghbrSendSizes[i][j], &nodeVarsSendBuffers[i][size]);
5083 size += nghbrSendSizes[i][j];
5084 }
5085 ASSERT(size == static_cast<MInt>(nodeVarsSendBuffers[i].size()), "Data size does not match buffer size.");
5086 }
5087 std::vector<MPI_Request> nodeVarsRecvRequests(m_noExchangeNghbrDomains);
5088 std::vector<MPI_Request> nodeVarsSendRequests(m_noExchangeNghbrDomains);
5089
5090 // Mpi-exchange buffer.
5091 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
5092 if(nodeVarsRecvBuffers[i].size() > 0) {
5093 MPI_Irecv(&nodeVarsRecvBuffers[i][0], nodeVarsRecvBuffers[i].size(), type_traits<MFloat>::mpiType(),
5094 m_exchangeNghbrDomains[i], m_exchangeNghbrDomains[i], mpiComm(), &nodeVarsRecvRequests[i], AT_,
5095 "nodeVarsRecvBuffers[i][0]");
5096 } else {
5097 nodeVarsRecvRequests[i] = MPI_REQUEST_NULL;
5098 }
5099 if(nodeVarsSendBuffers[i].size() > 0) {
5100 MPI_Isend(&nodeVarsSendBuffers[i][0], nodeVarsSendBuffers[i].size(), type_traits<MFloat>::mpiType(),
5101 m_exchangeNghbrDomains[i], domainId(), mpiComm(), &nodeVarsSendRequests[i], AT_,
5102 "nodeVarsSendBuffers[i][0]");
5103 } else {
5104 nodeVarsSendRequests[i] = MPI_REQUEST_NULL;
5105 }
5106 }
5107 // Since the send buffer will not get modified after this point, this wait
5108 // can be moved to a later point, it is posted here for readability only.
5109 MPI_Waitall(m_noExchangeNghbrDomains, &nodeVarsSendRequests[0], MPI_STATUSES_IGNORE, AT_);
5110
5111 // This wait can not be posted later than here, since the recvbuffer is
5112 // read after this!
5113 MPI_Waitall(m_noExchangeNghbrDomains, &nodeVarsRecvRequests[0], MPI_STATUSES_IGNORE, AT_);
5114 // Unpack recvbuffers into surfaces and trigger updates on element(s) at surfaces.
5115 for(MInt nghbrIndex = 0; nghbrIndex < m_noExchangeNghbrDomains; nghbrIndex++) {
5116 // Unpack recvbuffer.
5117 MInt size = 0;
5118 for(vector<MInt>::size_type j = 0; j < m_mpiSurfaces[nghbrIndex].size(); j++) {
5119 if(nghbrRecvSizes[nghbrIndex][j] < 1) {
5120 continue;
5121 }
5122 const MInt srfcId = m_mpiSurfaces[nghbrIndex][j];
5123 surfaceHasRecvd[srfcId] = 1.0;
5124 // Copy nodeVars data.
5125 copy_n(&nodeVarsRecvBuffers[nghbrIndex][size],
5126 nghbrRecvSizes[nghbrIndex][j],
5127 &m_surfaces.nodeVars(srfcId, 1 - extendSide));
5128 copy_n(&nodeVarsRecvBuffers[nghbrIndex][size],
5129 nghbrRecvSizes[nghbrIndex][j],
5130 &m_surfaces.nodeVars(srfcId, extendSide));
5131 // Set up update.
5132 const MInt recvElementId = m_surfaces.nghbrElementIds(srfcId, extendSide);
5133 // Coarse cells with fine neighbors on a different domain
5134 //("reverse h-Refined") update only if all child surfaces have received
5135 // updated nodeVars.
5136 MBool srfcIsReverseHrefTemp = false;
5137 for(MInt pos = 0; pos < noSurfs; pos++) {
5138 if(hReverseSurfaceId(srfcId, pos) > -1) {
5139 srfcIsReverseHrefTemp = true;
5140 }
5141 }
5142 const MBool srfcIsReverseHref = srfcIsReverseHrefTemp;
5143 MBool allSurfsReady = true;
5144 if(srfcIsReverseHref) {
5145 const MInt mainSurfId = m_elements.surfaceIds(recvElementId, donorDir);
5146 for(MInt pos = 0; pos < noSurfs; pos++) {
5147 if(surfaceHasRecvd[hReverseSurfaceId(mainSurfId, pos)] < 1) {
5148 allSurfsReady = false;
5149 }
5150 }
5151 }
5152 if(allSurfsReady) {
5153 MBool dummy; // this is not used here
5154 const MInt updateeElementId = m_surfaces.nghbrElementIds(srfcId, extendSide);
5155 // skip elements outside the extend limit coordinates
5156 if(!elementOutsideLimit[updateeElementId]) {
5157 updateNodeVariablesSingleElement(recvElementId, extendDir, hForwardSurfaceId, hReverseSurfaceId,
5158 elementUpdated, mpiSurfaceSendSize, dummy);
5159 elementUpdated[recvElementId] = true;
5160 }
5161 }
5162 size += nghbrRecvSizes[nghbrIndex][j];
5163 }
5164 ASSERT(size == static_cast<MInt>(nodeVarsRecvBuffers[nghbrIndex].size()),
5165 "Data size does not match buffer size.");
5166 }
5167 // The sendSurfaceBufferSize might be modified in the next iteration,
5168 // so we have to wait for this request no later than here.
5169 MPI_Waitall(m_noExchangeNghbrDomains, &metaDataSendRequests[0], MPI_STATUS_IGNORE, AT_);
5170 }
5171 m_log << "done" << std::endl;
5172 updateNodeVariables();
5173}
5174
5175
5185template <MInt nDim, class SysEqn>
5187 const MInt extendDir,
5188 MIntTensor hForwardSurfaceId,
5189 MIntTensor hReverseSurfaceId,
5190 std::vector<MBool>& elementUpdated,
5191 std::vector<MInt>& mpiSurfaceSendSize,
5192 MBool& noMoreUpdates) {
5193 // Precompute variables.
5194 const MInt* const noNodes1D = &m_elements.noNodes1D(0);
5195 const MInt* const surfaceNoNodes1D = &m_surfaces.noNodes1D(0);
5196 const MInt extendSide = extendDir % 2;
5197 const MInt extendDimension = (extendDir - extendSide) / 2;
5198 const MInt donorDir = 2 * extendDimension + (1 - extendSide);
5199 const MInt noVars = max(SysEqn::noNodeVars(), 1);
5200 const MInt noSurfs = 2 * (nDim - 1);
5201 const MInt maxNoNodes1D = noNodes1D[elementId];
5202 const MInt maxNoNodes1D3 = (nDim == 3) ? maxNoNodes1D : 1;
5203 MInt extendDomainId;
5204 MPI_Comm_rank(mpiComm(), &extendDomainId);
5205 // Set up Ids and data tensors.
5206 const MInt donorSrfcId = m_elements.surfaceIds(elementId, donorDir);
5207 const MInt recvSrfcId = m_elements.surfaceIds(elementId, extendDir);
5208 MFloatTensor nodeVars(&m_elements.nodeVars(elementId), maxNoNodes1D, maxNoNodes1D, maxNoNodes1D3, noVars);
5209 const MInt noNodesSecondSurfDim = extendDimension == 2 ? maxNoNodes1D : maxNoNodes1D3;
5210 MFloatTensor elemNodeVarsSlice(maxNoNodes1D, noNodesSecondSurfDim, noVars);
5211 /* const MFloatTensor donorNodeVars(&m_surfaces.nodeVars(donorSrfcId, 1 - extendSide), */
5212 /* maxNoNodes1D, noNodesSecondSurfDim, noVars); */
5213 MFloatTensor recvSurfaceNodeVarsMSide(&m_surfaces.nodeVars(recvSrfcId, extendSide), maxNoNodes1D,
5214 noNodesSecondSurfDim, noVars);
5215 MFloatTensor recvSurfaceNodeVarsPSide(&m_surfaces.nodeVars(recvSrfcId, 1 - extendSide), maxNoNodes1D,
5216 noNodesSecondSurfDim, noVars);
5217
5218 // Update receiver cell and its receiver side surface.
5219 const MBool pRefinedDonorSide = m_surfaces.polyDeg(donorSrfcId) > m_elements.polyDeg(elementId);
5220 const MBool pRefinedRecvSide = m_surfaces.polyDeg(recvSrfcId) > m_elements.polyDeg(elementId);
5221 const MBool srfcIsForwardHref = hForwardSurfaceId(donorSrfcId, 0) > -1;
5222
5223 // Get default node variables for those variables that are not extended
5224 MFloatScratchSpace defaultNodeVars(SysEqn::noNodeVars(), AT_, "defaultNodeVars");
5225 m_sysEqn.getDefaultNodeVars(defaultNodeVars.getPointer());
5226 // Store if variables needs to be extended
5227 MBoolScratchSpace extendNodeVar(SysEqn::noNodeVars(), AT_, "extendNodeVars");
5228 for(MInt iVars = 0; iVars < noVars; iVars++) {
5229 extendNodeVar[iVars] = m_sysEqn.extendNodeVar(iVars);
5230 }
5231
5232 // This abuses the fact that the coarse elements surface and the main position's fine surface
5233 // share the srfcID
5234 MBool srfcIsReverseHrefTemp = false;
5235 for(MInt pos = 0; pos < noSurfs; pos++) {
5236 if(hReverseSurfaceId(donorSrfcId, pos) > -1) {
5237 srfcIsReverseHrefTemp = true;
5238 }
5239 }
5240 const MBool srfcIsReverseHref = srfcIsReverseHrefTemp;
5241 MBool srfcIsMainPosTemp = false;
5242 for(MInt pos = 0; pos < noSurfs; pos++) {
5243 if(hReverseSurfaceId(donorSrfcId, pos) == donorSrfcId) {
5244 srfcIsMainPosTemp = true;
5245 }
5246 }
5247 const MBool srfcIsMainPos = srfcIsMainPosTemp;
5248 if(srfcIsForwardHref) { // One donor element, multiple recv/updatee elements
5249 // Call update for children, then proceed to update self.
5250 for(MInt pos = 1; pos < noSurfs; pos++) {
5251 const MInt hSrfcId = hForwardSurfaceId(donorSrfcId, pos);
5252 const MInt hElemId = m_surfaces.nghbrElementIds(hSrfcId, extendSide);
5253 updateNodeVariablesSingleElement(hElemId, extendDir, hForwardSurfaceId, hReverseSurfaceId, elementUpdated,
5254 mpiSurfaceSendSize, noMoreUpdates);
5255 }
5256 }
5257 // h(p) is excluded here, because h already includes p refinement(if necessary)
5258 if(!srfcIsReverseHref) { // one donor/one recv
5259 // project/copy from donorVars(fine surface) to element(coarse).
5260 if(pRefinedDonorSide) { // p projection required
5261 const MInt maxNoNodes1DFine = surfaceNoNodes1D[donorSrfcId];
5262 const MInt maxNoNodes1D3Fine = nDim == 3 ? maxNoNodes1DFine : 1;
5263 const MInt noNodesSecondSurfDimFine = extendDimension == 2 ? maxNoNodes1DFine : maxNoNodes1D3Fine;
5264 // P-ref mortar projection projects onto same size buffers (high degree),
5265 // even though lower p'nomial degree has fewer dofs.
5266 MFloatTensor projectedRight(maxNoNodes1DFine, noNodesSecondSurfDimFine, noVars);
5267 calcMortarProjection<dg::mortar::reverse, SysEqn::noNodeVars()>(donorSrfcId, donorDir,
5268 &m_surfaces.nodeVars(donorSrfcId, 1 - extendSide),
5269 &projectedRight[0], m_elements, m_surfaces);
5270 for(MInt a = 0; a < maxNoNodes1D; ++a) {
5271 for(MInt b = 0; b < maxNoNodes1D3; ++b) {
5272 for(MInt iVars = 0; iVars < noVars; iVars++) {
5273 elemNodeVarsSlice(a, b, iVars) = projectedRight(a, b, iVars);
5274 }
5275 }
5276 }
5277 } else { // no p projection required
5278 copy_n(&m_surfaces.nodeVars(donorSrfcId, 1 - extendSide),
5279 maxNoNodes1D * noNodesSecondSurfDim * noVars,
5280 &elemNodeVarsSlice[0]);
5281 }
5282 // copy nodeVars to element
5283 for(MInt a = 0; a < maxNoNodes1D; ++a) {
5284 for(MInt b = 0; b < maxNoNodes1D; ++b) {
5285 for(MInt c = 0; c < maxNoNodes1D3; ++c) {
5286 // determine which iterators are need for the surface-like elemNodeVarsSlice
5287 const MInt iNodes2D = extendDimension == 0 ? b : a;
5288 const MInt iNodes3D = extendDimension == 2 ? b : c;
5289 for(MInt iVars = 0; iVars < noVars; iVars++) {
5290 nodeVars(a, b, c, iVars) =
5291 (extendNodeVar[iVars]) ? elemNodeVarsSlice(iNodes2D, iNodes3D, iVars) : defaultNodeVars(iVars);
5292 }
5293 }
5294 }
5295 }
5296 // Mark element as updated.
5297 elementUpdated[elementId] = true;
5298 noMoreUpdates = false;
5299 // Copy from elem(coarse) and map to recvSurf(fine)
5300 if(pRefinedRecvSide) {
5301 // Set up projection to next surface.
5302 // Skip h-refined surfaces, as they will be handled separately.
5303 if(hForwardSurfaceId(recvSrfcId, 0) <= 0) {
5304 const MInt maxNoNodes1DSurf = surfaceNoNodes1D[recvSrfcId];
5305 const MInt maxNoNodes1D3Surf = nDim == 3 ? maxNoNodes1DSurf : 1;
5306 const MInt noNodesSecondRefSurfDim = extendDimension == 2 ? maxNoNodes1DSurf : maxNoNodes1D3Surf;
5307 MFloatTensor projected(maxNoNodes1DSurf, noNodesSecondRefSurfDim, noVars);
5308 // Project data to next surface.
5309 calcMortarProjection<dg::mortar::forward, SysEqn::noNodeVars()>(
5310 recvSrfcId, extendDir, &m_surfaces.nodeVars(donorSrfcId, 1 - extendSide), &projected[0], m_elements,
5311 m_surfaces);
5312 // Update next surface(both sides).
5313 copy_n(&projected[0],
5314 maxNoNodes1DSurf * noNodesSecondRefSurfDim * noVars,
5315 &m_surfaces.nodeVars(recvSrfcId, extendSide));
5316 copy_n(&projected[0],
5317 maxNoNodes1DSurf * noNodesSecondRefSurfDim * noVars,
5318 &m_surfaces.nodeVars(recvSrfcId, 1 - extendSide));
5319 // Mark recv surface for mpi exchange if on domain border.
5320 if(recvSrfcId >= m_mpiSurfacesOffset) {
5321 mpiSurfaceSendSize[recvSrfcId] = maxNoNodes1DSurf * maxNoNodes1D3Surf * SysEqn::noNodeVars();
5322 }
5323 }
5324 } else { // no projection needed, just copy to both sides of recv surface
5325 copy_n(&elemNodeVarsSlice[0], maxNoNodes1D * noNodesSecondSurfDim * noVars, &recvSurfaceNodeVarsMSide[0]);
5326 copy_n(&elemNodeVarsSlice[0], maxNoNodes1D * noNodesSecondSurfDim * noVars, &recvSurfaceNodeVarsPSide[0]);
5327 // Mark recv surface for mpi exchange if on domain border.
5328 if(recvSrfcId >= m_mpiSurfacesOffset) {
5329 mpiSurfaceSendSize[recvSrfcId] = maxNoNodes1D * maxNoNodes1D3 * SysEqn::noNodeVars();
5330 }
5331 }
5332 }
5333 if(srfcIsReverseHref && srfcIsMainPos) { // multiple donors, one receive element
5334 // Check if all four pos have updated means.
5335 MBool allDonorsHaveUpdatedMeans = true;
5336 for(MInt pos = 0; pos < noSurfs; pos++) {
5337 const MInt hElemId = m_surfaces.nghbrElementIds(hReverseSurfaceId(donorSrfcId, pos), 1 - extendSide);
5338 // This case happens if href happens across domain boundaries and is handled outside of this
5339 // function.
5340 if(hElemId < 0) {
5341 allDonorsHaveUpdatedMeans = true;
5342 continue;
5343 }
5344 if(elementUpdated[hElemId] == false) {
5345 allDonorsHaveUpdatedMeans = false;
5346 elementUpdated[elementId] = false;
5347 noMoreUpdates = false;
5348 }
5349 }
5350 // Only when all donor surfaces are updated, the recv element assembles its nodevars.
5351 if(allDonorsHaveUpdatedMeans) {
5352 const MInt maxNoNodes1DSurf = surfaceNoNodes1D[donorSrfcId];
5353 const MInt maxNoNodes1D3Surf = nDim == 3 ? maxNoNodes1DSurf : 1;
5354 const MInt noNodesSecondRefSurfDim = extendDimension == 2 ? maxNoNodes1DSurf : maxNoNodes1D3Surf;
5355 const MInt coarseNextSrfcId = m_elements.surfaceIds(elementId, extendDir);
5356 // mark refined cell as updated with hElemId
5357 elementUpdated[elementId] = true;
5358 // mark recv surface for mpi exchange if on domain border
5359 if(coarseNextSrfcId >= m_mpiSurfacesOffset) {
5360 mpiSurfaceSendSize[coarseNextSrfcId] = maxNoNodes1D * maxNoNodes1D3 * SysEqn::noNodeVars();
5361 }
5362 noMoreUpdates = false;
5363 MFloatTensor projectedSum(maxNoNodes1DSurf, noNodesSecondRefSurfDim, noVars);
5364 projectedSum.set(0.0);
5365 for(MInt pos = 0; pos < noSurfs; pos++) {
5366 const MInt hSrfId = hReverseSurfaceId(donorSrfcId, pos);
5367 MFloatTensor projected(maxNoNodes1DSurf, noNodesSecondRefSurfDim, noVars);
5368 projected.set(0.0);
5369 calcMortarProjection<dg::mortar::reverse, SysEqn::noNodeVars()>(
5370 hSrfId, donorDir, &m_surfaces.nodeVars(hSrfId, 1 - extendSide), &projected[0], m_elements, m_surfaces);
5371 for(MInt i = 0; i < maxNoNodes1D; i++) {
5372 for(MInt j = 0; j < noNodesSecondSurfDim; j++) {
5373 for(MInt k = 0; k < noVars; k++) {
5374 projectedSum(i, j, k) += projected(i, j, k);
5375 }
5376 }
5377 }
5378 }
5379 // Copy projected values to element.
5380 // This loop structure is suboptimal regarding memory acces, but easier to read.
5381 // If better performance is desired here, consider using separate loops for each possible
5382 // direction.
5383 MFloatTensor hNodeVarsLeft(&m_elements.nodeVars(elementId), maxNoNodes1D, maxNoNodes1D, maxNoNodes1D3, noVars);
5384 MFloatTensor hNodeVarsLeftSurf1(&m_surfaces.nodeVars(coarseNextSrfcId, extendSide), maxNoNodes1D,
5385 noNodesSecondSurfDim, noVars);
5386 MFloatTensor hNodeVarsLeftSurf2(&m_surfaces.nodeVars(coarseNextSrfcId, 1 - extendSide), maxNoNodes1D,
5387 noNodesSecondSurfDim, noVars);
5388 for(MInt a = 0; a < maxNoNodes1D; ++a) {
5389 for(MInt b = 0; b < maxNoNodes1D; ++b) {
5390 for(MInt c = 0; c < maxNoNodes1D3; ++c) {
5391 const MInt iNodes2D = extendDimension == 0 ? b : a;
5392 const MInt iNodes3D = extendDimension == 2 ? b : c;
5393 for(MInt iVars = 0; iVars < noVars; iVars++) {
5394 if(extendNodeVar[iVars]) {
5395 hNodeVarsLeft(a, b, c, iVars) = projectedSum(iNodes2D, iNodes3D, iVars);
5396 elemNodeVarsSlice(iNodes2D, iNodes3D, iVars) = projectedSum(iNodes2D, iNodes3D, iVars);
5397 hNodeVarsLeftSurf1(iNodes2D, iNodes3D, iVars) = projectedSum(iNodes2D, iNodes3D, iVars);
5398 hNodeVarsLeftSurf2(iNodes2D, iNodes3D, iVars) = projectedSum(iNodes2D, iNodes3D, iVars);
5399 } else {
5400 hNodeVarsLeft(a, b, c, iVars) = defaultNodeVars(iVars);
5401 elemNodeVarsSlice(iNodes2D, iNodes3D, iVars) = defaultNodeVars(iVars);
5402 hNodeVarsLeftSurf1(iNodes2D, iNodes3D, iVars) = defaultNodeVars(iVars);
5403 hNodeVarsLeftSurf2(iNodes2D, iNodes3D, iVars) = defaultNodeVars(iVars);
5404 }
5405 }
5406 }
5407 }
5408 }
5409 elementUpdated[elementId] = true;
5410 noMoreUpdates = false;
5411 }
5412 }
5413 // If the receiver surface is h(p)-refined(because the element after the receive element is),
5414 // copy the updated nodeVars to all child surfaces and project.
5415 // This guarantees that the next element can use these as is.
5416 if(hForwardSurfaceId(recvSrfcId, 0) > 0) {
5417 const MInt maxNoNodes1DFine = surfaceNoNodes1D[recvSrfcId];
5418 const MInt maxNoNodes1D3Fine = nDim == 3 ? maxNoNodes1DFine : 1;
5419 const MInt noNodesSecondFineSurfDim = extendDimension == 2 ? maxNoNodes1DFine : maxNoNodes1D3Fine;
5420 for(MInt pos = 0; pos < noSurfs; pos++) {
5421 const MInt hSrfId = hForwardSurfaceId(recvSrfcId, pos);
5422 copy_n(&elemNodeVarsSlice(0, 0, 0),
5423 maxNoNodes1D * noNodesSecondSurfDim * noVars,
5424 &m_surfaces.nodeVars(hSrfId, 1 - extendSide));
5425 }
5426 for(MInt pos = 0; pos < noSurfs; pos++) {
5427 const MInt hSrfId = hForwardSurfaceId(recvSrfcId, pos);
5428 MFloatTensor projected(maxNoNodes1DFine, noNodesSecondFineSurfDim, noVars);
5429 projected.set(0.0);
5430 calcMortarProjection<dg::mortar::forward, SysEqn::noNodeVars()>(
5431 hSrfId, extendDir, &m_surfaces.nodeVars(hSrfId, 1 - extendSide), &projected[0], m_elements, m_surfaces);
5432 MFloatTensor nodeVarsNextSrfPSide(&m_surfaces.nodeVars(hSrfId, extendSide), maxNoNodes1DFine,
5433 noNodesSecondFineSurfDim, noVars);
5434 MFloatTensor nodeVarsNextSrfMSide(&m_surfaces.nodeVars(hSrfId, 1 - extendSide), maxNoNodes1DFine,
5435 noNodesSecondFineSurfDim, noVars);
5436 for(MInt iNodes2D = 0; iNodes2D < maxNoNodes1DFine; iNodes2D++) {
5437 for(MInt iNodes3D = 0; iNodes3D < noNodesSecondFineSurfDim; iNodes3D++) {
5438 for(MInt iVars = 0; iVars < noVars; iVars++) {
5439 nodeVarsNextSrfPSide(iNodes2D, iNodes3D, iVars) = projected(iNodes2D, iNodes3D, iVars);
5440 nodeVarsNextSrfMSide(iNodes2D, iNodes3D, iVars) = projected(iNodes2D, iNodes3D, iVars);
5441 }
5442 }
5443 }
5444 // Mark next left surface of child element for mpi exchange if on domain border.
5445 if(recvSrfcId >= m_mpiSurfacesOffset) {
5446 mpiSurfaceSendSize[hSrfId] = maxNoNodes1DFine * maxNoNodes1D3Fine * noVars;
5447 }
5448 }
5449 }
5450}
5451
5452
5459template <MInt nDim, class SysEqn>
5461 TRACE();
5462
5463 // Get proper names from numeric properties
5464 const MString polynomialType = "Legendre";
5465 const MString integrationMethod = (m_dgIntegrationMethod == 0) ? "Gauss" : "Gauss-Lobatto";
5466
5467 using namespace maia::logtable;
5468
5469 // INIT SUMMARY FRAME
5470 Frame summary(" SOLVER " + std::to_string(solverId()) + " INITIALIZATION SUMMARY AT TIME STEP "
5471 + std::to_string(m_timeStep));
5472
5473 // PROBLEM SUMMARY GROUP
5474 Group& problem = summary.addGroup(" PROBLEM SUMMARY");
5475 problem.addData("System of equations", m_sysEqn.sysEqnName());
5476 problem.addData("Number of dimensions", nDim);
5477 Data& vars = problem.addData("Number of variables", m_sysEqn.noVars());
5478 for(MInt i = 0; i < m_sysEqn.noVars(); i++) {
5479 if(i == 0) {
5480 vars.addData("Conservative variable name(s)", m_sysEqn.consVarNames(i));
5481 } else {
5482 vars.addData("", m_sysEqn.consVarNames(i));
5483 }
5484 }
5485 problem.addData("Restart", m_restart);
5486 if(m_restart) {
5487 problem.addData("Initial condition", getRestartFileName(m_timeStep, m_useNonSpecifiedRestartFile));
5488 } else {
5489 problem.addData("Initial condition", m_sysEqn.m_initialCondition);
5490 }
5491 problem.addData("Start time (non-dimensionalized)", m_time);
5492 problem.addData("Final time (non-dimensionalized)", m_finalTime);
5493 problem.addData("CFL", m_sysEqn.cfl());
5494 problem.addData("Recalculation interval for time step", m_calcTimeStepInterval);
5495 problem.addData("Time step", m_timeStep);
5496 problem.addData("Maximum number of time steps", m_timeSteps);
5497
5498 // DISCRETIZATION SUMMARY GROUP
5499 Group& discret = summary.addGroup("DISCRETIZATION SUMMARY");
5500 discret.addData("Initial polynomial degree", m_initPolyDeg);
5501 discret.addData("Minimum polynomial degree (limit)", m_minPolyDeg);
5502 discret.addData("Maximum polynomial degree (limit)", m_maxPolyDeg);
5503 discret.addData("Polynomial type", polynomialType);
5504 discret.addData("Integration method", integrationMethod);
5505 MString timeIntegrationScheme;
5506 switch(m_dgTimeIntegrationScheme) {
5508 default:
5509 timeIntegrationScheme = "Carpenter 4/5";
5510 break;
5511 }
5513 timeIntegrationScheme = "Toulorge C 4/8";
5514 break;
5515 }
5517 timeIntegrationScheme = "Niegemann 4/14";
5518 break;
5519 }
5521 timeIntegrationScheme = "Niegemann 4/13";
5522 break;
5523 }
5525 timeIntegrationScheme = "Toulorge C 3/7";
5526 break;
5527 }
5529 timeIntegrationScheme = "Toulorge F 4/8";
5530 break;
5531 }
5532 }
5533 discret.addData("Time integration scheme", timeIntegrationScheme);
5534
5535 // PARALLELIZATION SUMMARY
5536 Group& parallel = summary.addGroup("PARALLELIZATION SUMMARY");
5537 parallel.addData("Domain id", domainId());
5538 parallel.addData("Number of neighbor domains", grid().noNeighborDomains());
5539 parallel.addData("Number of exchange neighbor domains", m_noExchangeNghbrDomains);
5540 parallel.addData("Total number of domains", noDomains());
5541
5542 // GRID SUMMARY LOCAL
5543 Group& gridLocal = summary.addGroup("GRID SUMMARY (LOCAL)");
5544 gridLocal.addData("Minimum used polynomial degree", m_statLocalMinPolyDeg);
5545 gridLocal.addData("Maximum used polynomial degree", m_statLocalMaxPolyDeg);
5546 gridLocal.addData("Average used polynomial degree", 1.0 * m_statLocalPolyDegSum / m_statLocalNoActiveCells);
5547 gridLocal.addBlank();
5548 gridLocal.addData("Minimum grid level", m_statLocalMinLevel);
5549 gridLocal.addData("Maximum grid level", m_statLocalMaxLevel);
5550 gridLocal.addBlank();
5551 Data& noCellsLocal = gridLocal.addData("Number of cells", m_statLocalNoCells);
5552 noCellsLocal.addData("internal cells", m_statLocalNoInternalCells);
5553 noCellsLocal.addData("halo cells", m_statLocalNoHaloCells);
5554 gridLocal.addData("Number of active cells", m_statLocalNoActiveCells);
5555 gridLocal.addData("Number of active DOFs", m_statLocalNoActiveDOFs);
5556 // Number of active DOFs per polynomial degree
5557 const MInt noPolyDegs = m_maxPolyDeg - m_minPolyDeg + 1;
5558 if(noPolyDegs > 1) {
5559 for(MInt i = 0; i < noPolyDegs; i++) {
5560 MString name = "Number of active DOFs polyDeg=" + std::to_string(m_minPolyDeg + i);
5561 gridLocal.addData(name, m_statLocalNoActiveDOFsPolyDeg[i]);
5562 }
5563 }
5564 gridLocal.addData("Max. number of cells (collector size)", m_statLocalMaxNoCells);
5565 gridLocal.addData("Memory utilization", 100.0 * m_statLocalNoCells / m_statLocalMaxNoCells);
5566 gridLocal.addBlank();
5567 gridLocal.addData("Number of elements", m_statLocalNoElements);
5568 gridLocal.addData("Number of helements", m_statLocalNoHElements);
5569 gridLocal.addBlank();
5570 Data& surfaces = gridLocal.addData("Number of surfaces", m_statLocalNoSurfaces);
5571 surfaces.addData("boundary surfaces", m_statLocalNoBoundarySurfaces);
5572 surfaces.addData("inner surfaces", m_statLocalNoInnerSurfaces);
5573 surfaces.addData("MPI surfaces", m_statLocalNoMpiSurfaces);
5574 gridLocal.addData("Max. number of surfaces (collector size)", m_statLocalMaxNoSurfaces);
5575 gridLocal.addData("Memory utilization", 100.0 * m_statLocalNoSurfaces / m_statLocalMaxNoSurfaces);
5576
5577 // GRID SUMMARY (GLOBAL)
5578 Group& gridGlobal = summary.addGroup("GRID SUMMARY (GLOBAL)");
5579 gridGlobal.addData("Minimum used polynomial degree", m_statGlobalMinPolyDeg);
5580 gridGlobal.addData("Maximum used polynomial degree", m_statGlobalMaxPolyDeg);
5581 gridGlobal.addData("Average used polynomial degree", 1.0 * m_statGlobalPolyDegSum / m_statGlobalNoActiveCells);
5582 gridGlobal.addBlank();
5583 gridGlobal.addData("Minimum grid level", m_statGlobalMinLevel);
5584 gridGlobal.addData("Maximum grid level", m_statGlobalMaxLevel);
5585 gridGlobal.addBlank();
5586 Data& noCellsGlbl = gridGlobal.addData("Number of cells", m_statGlobalNoCells);
5587 noCellsGlbl.addData("internal cells", m_statGlobalNoInternalCells);
5588 noCellsGlbl.addData("halo cells", m_statGlobalNoHaloCells);
5589 gridGlobal.addData("Number of active cells", m_statGlobalNoActiveCells);
5590 gridGlobal.addData("Number of active DOFs", m_statGlobalNoActiveDOFs);
5591 gridGlobal.addData("Max. number of cells (collector size)", m_statGlobalMaxNoCells);
5592 gridGlobal.addData("Memory utilization", 100.0 * m_statGlobalNoCells / m_statGlobalMaxNoCells);
5593 gridGlobal.addBlank();
5594 gridGlobal.addData("Number of elements", m_statGlobalNoElements);
5595 gridGlobal.addData("Number of helements", m_statGlobalNoHElements);
5596 gridGlobal.addBlank();
5597 Data& surfacesGlobal = gridGlobal.addData("Number of surfaces", m_statGlobalNoSurfaces);
5598 surfacesGlobal.addData("boundary surfaces", m_statGlobalNoBoundarySurfaces);
5599 surfacesGlobal.addData("inner surfaces", m_statGlobalNoInnerSurfaces);
5600 surfacesGlobal.addData("MPI surfaces", m_statGlobalNoMpiSurfaces);
5601 surfacesGlobal.addData("Surfaces marked for p-ref", m_statGlobalNoPrefSurfs);
5602 surfacesGlobal.addData("Surfaces marked for h-ref", m_statGlobalNoHrefSurfs);
5603 gridGlobal.addData("Max. number of surfaces (collector size)", m_statGlobalMaxNoSurfaces);
5604 gridGlobal.addData("Memory utilization", 100.0 * m_statGlobalNoSurfaces / m_statGlobalMaxNoSurfaces);
5605
5606 // BOUNDARY CONDITION SUMMARY
5607 Group& boundary = summary.addGroup("BOUNDARY CONDITIONS");
5608 for(auto&& bc : m_boundaryConditions) {
5609 boundary.addData(bc->name(), bc->id());
5610 }
5611
5612 // CUT-OFF BOUNDARY SUMMARY
5613 Group& cutOffBoundary = summary.addGroup("CUT-OFF BOUNDARY CONDITIONS");
5614 cutOffBoundary.addData("Enabled?", m_useCutOffBoundaries);
5615 if(m_useCutOffBoundaries) {
5616 std::array<MString, 6> dirs = {{"-x", "+x", "-y", "+y", "-z", "+z"}};
5617 for(MInt i = 0; i < 2 * nDim; i++) {
5618 const MString bc = dirs[i] + " (bcId: " + to_string(m_cutOffBoundaryConditionIds[i]) + ")";
5619 cutOffBoundary.addData(bc, m_statGlobalNoCutOffBoundarySurfaces[i]);
5620 }
5621 }
5622
5623 // SBP SUMMARY
5624 Group& sbp = summary.addGroup("SBP MODE");
5625 sbp.addData("Enabled?", m_sbpMode);
5626 sbp.addData("Operator", m_sbpOperator);
5627
5628 std::string s = summary.buildString();
5629
5630 // Print output to terminal only on root domain, print output to m_log on
5631 // all domains
5632 if(isMpiRoot()) {
5633 cout << s << std::endl;
5634 }
5635 m_log << s << endl;
5636}
5637
5638
5648template <MInt nDim, class SysEqn>
5650 TRACE();
5651
5652 stringstream ss;
5653 ss << setw(8) << setfill('0') << m_timeStep;
5654 saveSolutionFile(ss.str());
5655}
5656
5657
5667template <MInt nDim, class SysEqn>
5669 TRACE();
5670 RECORD_TIMER_START(m_timers[Timers::Accumulated]);
5671 RECORD_TIMER_START(m_timers[Timers::IO]);
5672 RECORD_TIMER_START(m_timers[Timers::SaveSolutionFile]);
5673
5674 m_log << "Saving solution file ... ";
5675
5676 // Create ParallelIo instance
5677 using namespace parallel_io;
5678 const MString fileName =
5679 outputDir() + "solution_" + getIdentifier(g_multiSolverGrid, "b") + suffix + ParallelIo::fileExt();
5680 const MInt noElements = m_elements.size();
5681 ParallelIo parallelIo(fileName, PIO_REPLACE, mpiComm());
5682
5683 // If adaptive refinement is active set to correct adapted grid file name
5684 if(hasAdaptivePref()) {
5685 parallelIo.setAttribute(suffix + grid().gridInputFileName(), "gridFile");
5686 } else {
5687 parallelIo.setAttribute(grid().gridInputFileName(), "gridFile");
5688 }
5689
5690 // for g_multiSolverGrids we need the solverId in the file
5691 parallelIo.setAttribute(solverId(), "solverId");
5692
5693 // Determine data offset for each element in output buffer
5694 MIntScratchSpace elementOffset(noElements, AT_, "elementOffset");
5695 MIntScratchSpace elementNodes(noElements, AT_, "elementNodes");
5696 for(MInt cellId = 0, offset = 0, elementId = 0; elementId < noElements; cellId++) {
5697 // If cellId is not the one of the current element (i.e. the current cell
5698 // does not have an element), add the default offset (based on initPolyDeg)
5699 if(cellId != m_elements.cellId(elementId)) {
5700 offset += ipow(m_initNoNodes1D, nDim);
5701 continue;
5702 }
5703
5704 // Otherwise use current offset for the current element
5705 elementOffset[elementId] = offset;
5706
5707 // Increase offset based on element polynomial degree
5708 const MInt noNodesXD = m_elements.noNodesXD(elementId);
5709 offset += noNodesXD;
5710
5711 // Set number of nodes to use later and proceed with next element
5712 elementNodes[elementId] = noNodesXD;
5713 elementId++;
5714 }
5715
5716 // Determine local data array size
5717 // array size = #nodes of elements + (#cells - #elements) * default cell size
5718 // (cells without elements use polyDeg = initPolyDeg)
5719 const MInt localNoNodes = m_noTotalNodesXD + (grid().noInternalCells() - noElements) * ipow(m_initNoNodes1D, nDim);
5720
5721 // Determine offset and global number of nodes
5722 ParallelIo::size_type nodesOffset, globalNoNodes;
5723 ParallelIo::calcOffset(localNoNodes, &nodesOffset, &globalNoNodes, mpiComm());
5724
5725 // Grid file name and solver type
5726 parallelIo.setAttribute("DG", "solverType");
5727 if(m_sbpMode) {
5728 parallelIo.setAttribute((MInt)m_sbpMode, "sbpMode");
5729 }
5730 parallelIo.setAttribute(m_timeStep, "timeStep");
5731 parallelIo.setAttribute(m_time, "time");
5732
5733 // Define arrays in file
5734 // Polyomial degree
5735 parallelIo.defineArray(PIO_UCHAR, "polyDegs", grid().domainOffset(noDomains()));
5736 // Number of nodes in SBP mode
5737 if(m_sbpMode) {
5738 parallelIo.defineArray(PIO_UCHAR, "noNodes1D", grid().domainOffset(noDomains()));
5739 }
5740
5741
5742 // Get information about integration method and polynomial type
5743 MString dgIntegrationMethod = m_sbpMode ? "DG_INTEGRATE_GAUSS_LOBATTO" : "DG_INTEGRATE_GAUSS";
5744 dgIntegrationMethod =
5745 Context::getSolverProperty<MString>("dgIntegrationMethod", m_solverId, AT_, &dgIntegrationMethod);
5746 MString dgPolynomialType = "DG_POLY_LEGENDRE";
5747 dgPolynomialType = Context::getSolverProperty<MString>("dgPolynomialType", m_solverId, AT_, &dgPolynomialType);
5748
5749 // Add integration method & polynomial type to grid file as attributes
5750 parallelIo.setAttribute(dgIntegrationMethod, "dgIntegrationMethod", "polyDegs");
5751 parallelIo.setAttribute(dgPolynomialType, "dgPolynomialType", "polyDegs");
5752
5753 // Set counter to get correct variable names
5754 MInt varId = 0;
5755
5756 // Solution
5757 for(MInt i = 0; i < m_sysEqn.noVars(); i++) {
5758 const MString name = "variables" + to_string(varId++);
5759 parallelIo.defineArray(PIO_FLOAT, name, globalNoNodes);
5760 parallelIo.setAttribute(m_sysEqn.consVarNames(i), "name", name);
5761 }
5762
5763 // Node variables
5764 if(m_saveNodeVariablesToSolutionFile) {
5765 for(MInt i = 0; i < SysEqn::noNodeVars(); i++) {
5766 const MString name = "variables" + to_string(varId++);
5767 parallelIo.defineArray(PIO_FLOAT, name, globalNoNodes);
5768 parallelIo.setAttribute(m_sysEqn.nodeVarNames(i), "name", name);
5769 }
5770 }
5771
5772 // Time derivative
5773 if(m_writeTimeDerivative) {
5774 for(MInt i = 0; i < m_sysEqn.noVars(); i++) {
5775 const MString name = "variables" + to_string(varId++);
5776 parallelIo.defineArray(PIO_FLOAT, name, globalNoNodes);
5777 parallelIo.setAttribute(m_sysEqn.consVarNames(i) + "_tDeriv", "name", name);
5778 }
5779 }
5780
5781 if(useSponge() && m_writeSpongeEta > 0) {
5782 const MString name = "variables" + to_string(varId);
5783 parallelIo.defineArray(PIO_FLOAT, name, globalNoNodes);
5784 parallelIo.setAttribute("spongeEta", "name", name);
5785 }
5786
5787 // Write data to disk
5788 // Create scratch space with polynomial degree and write it to file
5789 parallelIo.setOffset(grid().noInternalCells(), grid().domainOffset(domainId()));
5790 MUcharScratchSpace polyDegs(grid().noInternalCells(), FUN_, "polyDegs");
5791 fill_n(&polyDegs[0], grid().noInternalCells(), m_initPolyDeg);
5792 for(MInt elementId = 0; elementId < noElements; elementId++) {
5793 const MInt cellId = m_elements.cellId(elementId);
5794 polyDegs.p[cellId] = m_elements.polyDeg(elementId);
5795 }
5796 parallelIo.writeArray(polyDegs.begin(), "polyDegs");
5797
5798 // Create scratch space with noNodes and write it to file
5799 if(m_sbpMode) {
5800 parallelIo.setOffset(grid().noInternalCells(), grid().domainOffset(domainId()));
5801 MUcharScratchSpace noNodes1D(grid().noInternalCells(), FUN_, "noNodes1D");
5802 fill_n(&noNodes1D[0], grid().noInternalCells(), m_initNoNodes1D);
5803 for(MInt elementId = 0; elementId < noElements; elementId++) {
5804 const MInt cellId = m_elements.cellId(elementId);
5805 noNodes1D.p[cellId] = m_elements.noNodes1D(elementId);
5806 }
5807 parallelIo.writeArray(noNodes1D.begin(), "noNodes1D");
5808 }
5809
5810 varId = 0;
5811 parallelIo.setOffset(localNoNodes, nodesOffset);
5812
5813 // Solution
5814 for(MInt i = 0; i < m_sysEqn.noVars(); i++) {
5815 MFloatScratchSpace buffer(localNoNodes, AT_, "buffer");
5816 fill(buffer.begin(), buffer.end(), 0.0);
5817 for(MInt e = 0; e < noElements; e++) {
5818 MFloat* const b = &buffer[elementOffset[e]];
5819 const MFloat* const v = &m_elements.variables(e) + i;
5820 for(MInt n = 0; n < elementNodes[e]; n++) {
5821 b[n] = v[n * m_sysEqn.noVars()];
5822 }
5823 }
5824 const MString name = "variables" + to_string(varId++);
5825 parallelIo.writeArray(&buffer[0], name);
5826 }
5827
5828 // Node variables
5829 if(m_saveNodeVariablesToSolutionFile) {
5830 for(MInt i = 0; i < SysEqn::noNodeVars(); i++) {
5831 MFloatScratchSpace buffer(localNoNodes, AT_, "buffer");
5832 fill(buffer.begin(), buffer.end(), 0.0);
5833 for(MInt e = 0; e < noElements; e++) {
5834 MFloat* const b = &buffer[elementOffset[e]];
5835 const MFloat* const v = &m_elements.nodeVars(e) + i;
5836 for(MInt n = 0; n < elementNodes[e]; n++) {
5837 b[n] = v[n * SysEqn::noNodeVars()];
5838 }
5839 }
5840 const MString name = "variables" + to_string(varId++);
5841 parallelIo.writeArray(&buffer[0], name);
5842 }
5843 }
5844
5845 // Time derivative
5846 if(m_writeTimeDerivative) {
5847 for(MInt i = 0; i < m_sysEqn.noVars(); i++) {
5848 MFloatScratchSpace buffer(localNoNodes, AT_, "buffer");
5849 fill(buffer.begin(), buffer.end(), 0.0);
5850 for(MInt e = 0; e < noElements; e++) {
5851 MFloat* const b = &buffer[elementOffset[e]];
5852 const MFloat* const r = &m_elements.rightHandSide(e) + i;
5853 for(MInt n = 0; n < elementNodes[e]; n++) {
5854 b[n] = r[n * m_sysEqn.noVars()];
5855 }
5856 }
5857 const MString name = "variables" + to_string(varId++);
5858 parallelIo.writeArray(&buffer[0], name);
5859 }
5860 }
5861
5862 // SpongeEta
5863 if(useSponge() && m_writeSpongeEta > 0) {
5864 const MInt noSpongeElements = sponge().noSpongeElements();
5865 MFloatScratchSpace buffer(localNoNodes, AT_, "buffer");
5866 fill(buffer.begin(), buffer.end(), 0.0);
5867 for(MInt e = 0; e < noSpongeElements; e++) {
5868 MInt elementId = sponge().elementId(e);
5869 MFloat* const b = &buffer[elementOffset[elementId]];
5870 for(MInt n = 0; n < elementNodes[elementId]; n++) {
5871 b[n] = sponge().spongeEta(e, n);
5872 }
5873 }
5874 const MString name = "variables" + to_string(varId);
5875 parallelIo.writeArray(&buffer[0], name);
5876 }
5877 m_log << "done" << endl;
5878
5879 RECORD_TIMER_STOP(m_timers[Timers::SaveSolutionFile]);
5880 RECORD_TIMER_STOP(m_timers[Timers::IO]);
5881 RECORD_TIMER_STOP(m_timers[Timers::Accumulated]);
5882}
5883
5884
5892template <MInt nDim, class SysEqn>
5894 TRACE();
5895 RECORD_TIMER_START(m_timers[Timers::Accumulated]);
5896 RECORD_TIMER_START(m_timers[Timers::IO]);
5897 RECORD_TIMER_START(m_timers[Timers::SaveRestartFile]);
5898
5899 // Determine file name and grid file name
5900 const MString fileName = getRestartFileName(m_timeStep, m_useNonSpecifiedRestartFile);
5901
5902 stringstream ss;
5903 ss << "restart_" << getIdentifier(g_multiSolverGrid, "b") << setw(8) << setfill('0') << m_timeStep;
5904 const MString suffix = ss.str();
5905
5906 // Determine data offset for each element in output buffer
5907 const MInt noElements = m_elements.size();
5908 MIntScratchSpace elementOffset(noElements, AT_, "elementOffset");
5909 MIntScratchSpace elementNodes(noElements, AT_, "elementNodes");
5910 for(MInt cellId = 0, offset = 0, elementId = 0; elementId < noElements; cellId++) {
5911 // If cellId is not the one of the current element (i.e. the current cell
5912 // does not have an element), add the default offset (based on initPolyDeg)
5913 if(cellId != m_elements.cellId(elementId)) {
5914 offset += ipow(m_initNoNodes1D, nDim);
5915 continue;
5916 }
5917
5918 // Otherwise use current offset for the current element
5919 elementOffset[elementId] = offset;
5920
5921 // Increase offset based on element polynomial degree
5922 const MInt noNodesXD = m_elements.noNodesXD(elementId);
5923 offset += noNodesXD;
5924
5925 // Set number of nodes to use later and proceed with next element
5926 elementNodes[elementId] = noNodesXD;
5927 elementId++;
5928 }
5929
5930 // Determine local data array size
5931 // array size = #nodes of elements + (#cells - #elements) * default cell size
5932 // (cells without elements use polyDeg = initPolyDeg)
5933 const MInt localNoNodes = m_noTotalNodesXD + (grid().noInternalCells() - noElements) * ipow(m_initNoNodes1D, nDim);
5934
5935 // Create data out object to save data to disk
5936 using namespace parallel_io;
5937 ParallelIo parallelIo(fileName, PIO_REPLACE, mpiComm());
5938
5939 // Set attributes
5940 // If adaptive refinement is active set to correct adapted grid file name
5941 if(hasAdaptivePref()) {
5942 parallelIo.setAttribute(suffix + grid().gridInputFileName(), "gridFile");
5943 } else {
5944 parallelIo.setAttribute(grid().gridInputFileName(), "gridFile");
5945 }
5946 parallelIo.setAttribute("DG", "solverType");
5947 if(m_sbpMode) {
5948 parallelIo.setAttribute((MInt)m_sbpMode, "sbpMode");
5949 }
5950 parallelIo.setAttribute(solverId(), "solverId");
5951
5952 // Set time variables
5953 parallelIo.defineScalar(PIO_INT, "timeStep");
5954 parallelIo.defineScalar(PIO_FLOAT, "time");
5955 parallelIo.defineScalar(PIO_FLOAT, "dt");
5956
5957 // Determine offset and global number of nodes
5958 ParallelIo::size_type nodesOffset, globalNoNodes;
5959 ParallelIo::calcOffset(localNoNodes, &nodesOffset, &globalNoNodes, mpiComm());
5960
5961 // Define arrays in file
5962 // Polynomial degree
5963 parallelIo.defineArray(PIO_UCHAR, "polyDegs", grid().domainOffset(noDomains()));
5964 // Number of nodes in SBP mode
5965 if(m_sbpMode) {
5966 parallelIo.defineArray(PIO_UCHAR, "noNodes1D", grid().domainOffset(noDomains()));
5967 }
5968
5969 // Get information about integration method and polynomial type
5970 MString dgIntegrationMethod = m_sbpMode ? "DG_INTEGRATE_GAUSS_LOBATTO" : "DG_INTEGRATE_GAUSS";
5971 dgIntegrationMethod =
5972 Context::getSolverProperty<MString>("dgIntegrationMethod", m_solverId, AT_, &dgIntegrationMethod);
5973 MString dgPolynomialType = "DG_POLY_LEGENDRE";
5974 dgPolynomialType = Context::getSolverProperty<MString>("dgPolynomialType", m_solverId, AT_, &dgPolynomialType);
5975 // Add integration method & polynomial type to restart file as attributes
5976 parallelIo.setAttribute(dgIntegrationMethod, "dgIntegrationMethod", "polyDegs");
5977 parallelIo.setAttribute(dgPolynomialType, "dgPolynomialType", "polyDegs");
5978
5979 // Set counter to get correct variable names
5980 MInt varId = 0;
5981
5982 // Solution
5983 for(MInt i = 0; i < m_sysEqn.noVars(); i++) {
5984 const MString name = "variables" + to_string(varId++);
5985 parallelIo.defineArray(PIO_FLOAT, name, globalNoNodes);
5986 parallelIo.setAttribute(m_sysEqn.consVarNames(i), "name", name);
5987 }
5988
5989 // Node variables
5990 if(SysEqn::hasTimeDependentNodeVars()) {
5991 for(MInt i = 0; i < SysEqn::noNodeVars(); i++) {
5992 const MString name = "variables" + to_string(varId++);
5993 parallelIo.defineArray(PIO_FLOAT, name, globalNoNodes);
5994 parallelIo.setAttribute(m_sysEqn.nodeVarNames(i), "name", name);
5995 }
5996 }
5997
5998 // Note: kept here to have a template for writing a data array that is only defined for all
5999 // elements but should be filled and written on a cell basis for visualization
6000 // Coupling variables
6001 // if (hasCoupling()) {
6002 // for (auto& cc : m_couplingConditions) {
6003 // for (MInt i = 0; i < cc->noRestartVars(); i++) {
6004 // const MString name = "variables" + to_string(varId++);
6005 // parallelIo.defineArray(PIO_FLOAT, name, globalNoNodes);
6006 // parallelIo.setAttribute(cc->restartVarName(i), "name", name);
6007 // parallelIo.setAttribute("couplingCondition", "type", name);
6008 // parallelIo.setAttribute(cc->name(), "ccName", name);
6009 // parallelIo.setAttribute(cc->id(), "ccId", name);
6010 // }
6011 // }
6012 //}
6013
6014 // Boundary conditions
6015 MInt bcId = 0;
6016 const MInt noBcIds = m_boundaryConditions.size();
6017 std::vector<ParallelIo::size_type> bcNodesOffset(noBcIds, 0), bcLocalNoNodes(noBcIds, 0), bcGlobalNoNodes(noBcIds, 0);
6018
6019 for(auto&& bc : m_boundaryConditions) {
6020 const MInt bcNoVars = bc->noRestartVars();
6021 if(bcNoVars > 0) {
6022 bcLocalNoNodes[bcId] = bc->getLocalNoNodes();
6023 ParallelIo::calcOffset(bcLocalNoNodes[bcId], &bcNodesOffset[bcId], &bcGlobalNoNodes[bcId], mpiComm());
6024 for(MInt i = 0; i < bcNoVars; i++) {
6025 const MString name = "variables" + to_string(varId++);
6026 parallelIo.defineArray(PIO_FLOAT, name, bcGlobalNoNodes[bcId]);
6027 parallelIo.setAttribute(bc->restartVarName(i), "name", name);
6028 }
6029 }
6030 bcId++;
6031 }
6032
6033 // Write data to disk
6034
6035 // Write time variables to file
6036 parallelIo.writeScalar(m_timeStep, "timeStep");
6037 parallelIo.writeScalar(m_time, "time");
6038 parallelIo.writeScalar(m_dt, "dt");
6039
6040 // Create scratch space with polynomial degree and write it to file
6041 parallelIo.setOffset(grid().noInternalCells(), grid().domainOffset(domainId()));
6042 MUcharScratchSpace polyDegs(grid().noInternalCells(), FUN_, "polyDegs");
6043 fill_n(&polyDegs[0], grid().noInternalCells(), m_initPolyDeg);
6044 for(MInt elementId = 0; elementId < noElements; elementId++) {
6045 const MInt cellId = m_elements.cellId(elementId);
6046 polyDegs.p[cellId] = m_elements.polyDeg(elementId);
6047 }
6048 parallelIo.writeArray(polyDegs.begin(), "polyDegs");
6049
6050 // Create scratch space with noNodes and write it to file
6051 if(m_sbpMode) {
6052 parallelIo.setOffset(grid().noInternalCells(), grid().domainOffset(domainId()));
6053 MUcharScratchSpace noNodes1D(grid().noInternalCells(), FUN_, "noNodes1D");
6054 fill_n(&noNodes1D[0], grid().noInternalCells(), m_initNoNodes1D);
6055 for(MInt elementId = 0; elementId < noElements; elementId++) {
6056 const MInt cellId = m_elements.cellId(elementId);
6057 noNodes1D.p[cellId] = m_elements.noNodes1D(elementId);
6058 }
6059 parallelIo.writeArray(noNodes1D.begin(), "noNodes1D");
6060 }
6061
6062 varId = 0;
6063 parallelIo.setOffset(localNoNodes, nodesOffset);
6064
6065 // Solution
6066 for(MInt i = 0; i < m_sysEqn.noVars(); i++) {
6067 // Solution
6068 MFloatScratchSpace buffer(localNoNodes, AT_, "buffer");
6069 fill(buffer.begin(), buffer.end(), 0.0);
6070 for(MInt e = 0; e < noElements; e++) {
6071 MFloat* const b = &buffer[elementOffset[e]];
6072 const MFloat* const v = &m_elements.variables(e) + i;
6073 for(MInt n = 0; n < elementNodes[e]; n++) {
6074 b[n] = v[n * m_sysEqn.noVars()];
6075 }
6076 }
6077 const MString name = "variables" + to_string(varId++);
6078 parallelIo.writeArray(&buffer[0], name);
6079 }
6080
6081 // Node variables
6082 if(SysEqn::hasTimeDependentNodeVars()) {
6083 for(MInt i = 0; i < SysEqn::noNodeVars(); i++) {
6084 MFloatScratchSpace buffer(localNoNodes, AT_, "buffer");
6085 fill(buffer.begin(), buffer.end(), 0.0);
6086 for(MInt e = 0; e < noElements; e++) {
6087 MFloat* const b = &buffer[elementOffset[e]];
6088 const MFloat* const v = &m_elements.nodeVars(e) + i;
6089 for(MInt n = 0; n < elementNodes[e]; n++) {
6090 b[n] = v[n * SysEqn::noNodeVars()];
6091 }
6092 }
6093 const MString name = "variables" + to_string(varId++);
6094 parallelIo.writeArray(&buffer[0], name);
6095 }
6096 }
6097
6098 // Note: keep this, see comment above at definition of coupling variables
6099 // Coupling variables
6100 // if (hasCoupling()) {
6101 // for (auto& cc : m_couplingConditions) {
6102 // for (MInt i = 0; i < cc->noRestartVars(); i++) {
6103 // // Load restart variable from coupling class
6104 // MFloatScratchSpace buffer(localNoNodes, AT_, "buffer");
6105 // cc->getRestartVariable(i, &buffer[0]);
6106 //
6107 // // Re-arrange data in buffer to add gaps for each cell without element
6108 // MInt offset = m_noTotalNodesXD;
6109 // for (MInt e = noElements - 1; e >= 0; e--) {
6110 // // Update offset
6111 // const MInt noNodesXD = m_elements.noNodesXD(e);
6112 // offset -= noNodesXD;
6113 //
6114 // // Create pointers to data
6115 // MFloat* const source = &buffer[offset];
6116 // MFloat* const destination = &buffer[elementOffset[e]];
6117 //
6118 // // No copy operation needed if pointers are equal
6119 // if (destination == source) {
6120 // continue;
6121 // }
6122 //
6123 // // Copy (move) data
6124 // copy_backward(source, source + noNodesXD, destination + noNodesXD);
6125 //
6126 // // Fill up original storage location with zeros
6127 // // The min(...) expression makes sure that
6128 // // - if the original and the final storage location overlap, only the
6129 // // values that are not in the final storage location are reset
6130 // // - if the original and the final storage location DO NOT overlap,
6131 // // - the entire original region is reset with zeros
6132 // fill_n(source, min(static_cast<MLong>(noNodesXD), destination - source), 0.0);
6133 // }
6134 //
6135 // // Write data from buffer to file
6136 // const MString name = "variables" + to_string(varId++);
6137 // parallelIo.writeArray(&buffer[0], name);
6138 // }
6139 // }
6140 //}
6141
6142 // Boundary conditions
6143 bcId = 0;
6144 for(auto&& bc : m_boundaryConditions) {
6145 parallelIo.setOffset(bcLocalNoNodes[bcId], bcNodesOffset[bcId]);
6146 for(MInt i = 0; i < bc->noRestartVars(); i++) {
6147 // Avoid dereferencing a zero length array
6148 MFloatScratchSpace buffer(max(bcLocalNoNodes[bcId], 1l), AT_, "buffer");
6149 fill(buffer.begin(), buffer.end(), 0.0);
6150
6151 // Get boundary condition restart variable
6152 bc->getRestartVariable(i, &buffer[0]);
6153 const MString name = "variables" + to_string(varId++);
6154 parallelIo.writeArray(&buffer[0], name);
6155 }
6156 bcId++;
6157 }
6158
6159 RECORD_TIMER_STOP(m_timers[Timers::SaveRestartFile]);
6160 RECORD_TIMER_STOP(m_timers[Timers::IO]);
6161 RECORD_TIMER_STOP(m_timers[Timers::Accumulated]);
6162}
6163
6164
6172template <MInt nDim, class SysEqn>
6174 TRACE();
6175
6176 m_log << "Loading restart file... ";
6177
6178 // Determine file name and grid file name
6179 const MString fileName = getRestartFileName(m_restartTimeStep, m_useNonSpecifiedRestartFile);
6180
6181 // Create big data object to load data from disk
6182 using namespace parallel_io;
6183 ParallelIo parallelIo(fileName, PIO_READ, mpiComm());
6184
6185 // Get attributes
6186 MString gridFileName;
6187 parallelIo.getAttribute(&gridFileName, "gridFile");
6188
6189 // Get time variables
6190 parallelIo.readScalar(&m_timeStep, "timeStep");
6191 parallelIo.readScalar(&m_time, "time");
6192 parallelIo.readScalar(&m_dt, "dt");
6193
6194 // Load polynomial degrees
6195 MIntScratchSpace polyDegs(grid().noInternalCells(), AT_, "buffer");
6196 if(!parallelIo.hasDataset("polyDegs", 1)) {
6197 TERMM(1, "ERROR: restart file is not valid (missing polynomial degree data).");
6198 }
6199 parallelIo.setOffset(grid().noInternalCells(), grid().domainOffset(domainId()));
6200 parallelIo.readArray(&polyDegs[0], "polyDegs");
6201
6202 // update elements
6203 const MInt noElements = m_elements.size();
6204 m_noTotalNodesXD = 0;
6205 for(MInt elementId = 0; elementId < noElements; elementId++) {
6206 const MInt cellId = m_elements.cellId(elementId);
6207 const MInt polyDeg = polyDegs[cellId];
6208 const MInt noNodes1D = polyDeg + 1;
6209 const MInt noNodesXD = ipow(polyDeg + 1, nDim);
6210 m_elements.polyDeg(elementId) = polyDeg;
6211 m_elements.noNodes1D(elementId) = noNodes1D;
6212 m_noTotalNodesXD += noNodesXD;
6213 }
6214
6215 // Determine data offset for each element in output buffer
6216 MIntScratchSpace elementOffset(noElements, AT_, "elementOffset");
6217 for(MInt cellId = 0, offset = 0, elementId = 0; elementId < noElements; cellId++) {
6218 // If cellId is not the one of the current element (i.e. the current cell
6219 // does not have an element), add the default offset (based on initPolyDeg)
6220 if(cellId != m_elements.cellId(elementId)) {
6221 offset += ipow(m_initNoNodes1D, nDim);
6222 continue;
6223 }
6224
6225 // Otherwise use current offset for the current element
6226 elementOffset[elementId] = offset;
6227
6228 // Increase offset based on element polynomial degree
6229 const MInt noNodesXD = m_elements.noNodesXD(elementId);
6230 offset += noNodesXD;
6231
6232 // Set number of nodes to use later and proceed with next element
6233 elementId++;
6234 }
6235
6236 // Set data offsets and array sizes
6237 // Determine local data array size
6238 // array size = #nodes of elements + (#cells - #elements) * default cell size
6239 // (cells without elements use polyDeg = initPolyDeg)
6240 const MInt localNoNodes = m_noTotalNodesXD + (grid().noInternalCells() - noElements) * ipow(m_initNoNodes1D, nDim);
6241
6242 // Determine offset and global number of nodes
6243 ParallelIo::size_type nodesOffset, globalNoNodes;
6244 ParallelIo::calcOffset(localNoNodes, &nodesOffset, &globalNoNodes, mpiComm());
6245
6246 // Load data from disk
6247 parallelIo.setOffset(localNoNodes, nodesOffset);
6248 MInt varId = 0;
6249
6250 // Solution
6251 for(MInt i = 0; i < m_sysEqn.noVars(); i++) {
6252 MFloatScratchSpace buffer(localNoNodes, AT_, "buffer");
6253 const MString name = "variables" + to_string(varId++);
6254 parallelIo.readArray(&buffer[0], name);
6255 for(MInt e = 0; e < noElements; e++) {
6256 const MFloat* const b = &buffer[elementOffset[e]];
6257 MFloat* const v = &m_elements.variables(e) + i;
6258 const MInt noNodesXD = m_elements.noNodesXD(e);
6259 for(MInt n = 0; n < noNodesXD; n++) {
6260 v[n * m_sysEqn.noVars()] = b[n];
6261 }
6262 }
6263 }
6264
6265 // Node variables
6266 if(SysEqn::hasTimeDependentNodeVars()) {
6267 for(MInt i = 0; i < m_sysEqn.noNodeVars(); i++) {
6268 MFloatScratchSpace buffer(localNoNodes, AT_, "buffer");
6269 const MString name = "variables" + to_string(varId++);
6270 parallelIo.readArray(&buffer[0], name);
6271 for(MInt e = 0; e < noElements; e++) {
6272 const MFloat* const b = &buffer[elementOffset[e]];
6273 MFloat* const v = &m_elements.nodeVars(e) + i;
6274 const MInt noNodesXD = m_elements.noNodesXD(e);
6275 for(MInt n = 0; n < noNodesXD; n++) {
6276 v[n * m_sysEqn.noNodeVars()] = b[n];
6277 }
6278 }
6279 }
6280 }
6281
6282 // Note: keep this, might be useful as a reference sometime
6283 // Coupling variables
6284 // if (hasCoupling()) {
6285 // for (auto& cc : m_couplingConditions) {
6286 // for (MInt i = 0; i < cc->noRestartVars(); i++) {
6287 // // Load data into local buffer
6288 // MFloatScratchSpace buffer(localNoNodes, AT_, "buffer");
6289 // const MString name = "variables" + to_string(varId++);
6290 // parallelIo.readArray(&buffer[0], name);
6291 //
6292 // // Re-arrange data in buffer to make it "gap-less", i.e. for each
6293 // // element the data is adjacent to the neighboring elements
6294 // MInt offset = 0;
6295 // for (MInt e = 0; e < noElements; e++) {
6296 // // Create pointers to data
6297 // const MFloat* const b = &buffer[elementOffset[e]];
6298 // MFloat* const v = &buffer[offset];
6299 //
6300 // // Update offset
6301 // const MInt noNodesXD = m_elements.noNodesXD(e);
6302 // offset += noNodesXD;
6303 //
6304 // // No copy operation needed if pointers are equal
6305 // if (b == v) {
6306 // continue;
6307 // }
6308 //
6309 // // Copy (move) data
6310 // copy(b, b + noNodesXD, v);
6311 // }
6312 //
6313 // // Store restart variable in coupling class
6314 // cc->setRestartVariable(i, &buffer[0]);
6315 // }
6316 // }
6317 //}
6318
6319 // Boundary conditions
6320 for(auto&& bc : m_boundaryConditions) {
6321 const MInt bcNoVars = bc->noRestartVars();
6322 if(bcNoVars > 0) {
6323 const MInt bcLocalNoNodes = bc->getLocalNoNodes();
6324 ParallelIo::size_type bcNodesOffset, bcGlobalNoNodes;
6325 ParallelIo::calcOffset(bcLocalNoNodes, &bcNodesOffset, &bcGlobalNoNodes, mpiComm());
6326 parallelIo.setOffset(bcLocalNoNodes, bcNodesOffset);
6327 for(MInt i = 0; i < bcNoVars; i++) {
6328 // Avoid dereferencing a zero length array
6329 MFloatScratchSpace buffer(max(bcLocalNoNodes, 1), AT_, "buffer");
6330 const MString name = "variables" + to_string(varId++);
6331 parallelIo.readArray(&buffer[0], name);
6332
6333 // Store restart variable in boundary condition
6334 bc->setRestartVariable(i, &buffer[0]);
6335 }
6336 }
6337 }
6338
6339 m_log << "done" << endl;
6340}
6341
6342
6360template <MInt nDim, class SysEqn>
6362 const MInt noVars,
6363 const vector<MString>& varNames,
6364 const MFloat* const data) const {
6365 TRACE();
6366 CHECK_TIMERS_IO();
6367
6368 // Sanity check
6369 if(noVars < 1) {
6370 TERMM(1, "noVars must by >= 1");
6371 }
6372
6373 // Determine data offset for each element in output buffer
6374 const MInt noElements = m_elements.size();
6375 MIntScratchSpace elementOffset(noElements, AT_, "elementOffset");
6376 MIntScratchSpace elementNodes(noElements, AT_, "elementNodes");
6377 for(MInt cellId = 0, offset = 0, elementId = 0; elementId < noElements; cellId++) {
6378 // If cellId is not the one of the current element (i.e. the current cell
6379 // does not have an element), add the default offset (based on initPolyDeg)
6380 if(cellId != m_elements.cellId(elementId)) {
6381 offset += ipow(m_initNoNodes1D, nDim);
6382 continue;
6383 }
6384
6385 // Otherwise use current offset for the current element
6386 elementOffset[elementId] = offset;
6387
6388 // Increase offset based on element polynomial degree
6389 const MInt noNodesXD = m_elements.noNodesXD(elementId);
6390 offset += noNodesXD;
6391
6392 // Set number of nodes to use later and proceed with next element
6393 elementNodes[elementId] = noNodesXD;
6394 elementId++;
6395 }
6396
6397 // Determine local data array size
6398 // array size = #nodes of elements + (#cells - #elements) * default cell size
6399 // (cells without elements use polyDeg = initPolyDeg)
6400 const MInt localNoNodes = m_noTotalNodesXD + (grid().noInternalCells() - noElements) * ipow(m_initNoNodes1D, nDim);
6401
6402 // Create data out object to save data to disk
6403 const MString fileName = outputDir() + fileNameBase + ParallelIo::fileExt();
6404 using namespace parallel_io;
6405 ParallelIo parallelIo(fileName, PIO_REPLACE, mpiComm());
6406
6407 // Set attributes
6408 parallelIo.setAttribute(grid().gridInputFileName(), "gridFile");
6409 parallelIo.setAttribute("DG", "solverType");
6410 if(m_sbpMode) {
6411 parallelIo.setAttribute((MInt)m_sbpMode, "sbpMode");
6412 }
6413 // @ansgar TODO labels:DG,toenhance change this in the future to always write the solver id, this will change a lot
6414 // of reference files, e.g., the nodevars
6415 if(grid().raw().treeb().noSolvers() > 1) {
6416 parallelIo.setAttribute(solverId(), "solverId");
6417 }
6418
6419 // Determine offset and global number of nodes
6420 ParallelIo::size_type nodesOffset, globalNoNodes;
6421 ParallelIo::calcOffset(localNoNodes, &nodesOffset, &globalNoNodes, mpiComm());
6422
6423 // Define arrays in file
6424 // Polyomial degree
6425 parallelIo.defineArray(PIO_UCHAR, "polyDegs", grid().domainOffset(noDomains()));
6426 // Number of nodes in SBP mode
6427 if(m_sbpMode) {
6428 parallelIo.defineArray(PIO_UCHAR, "noNodes1D", grid().domainOffset(noDomains()));
6429 }
6430
6431 // Get information about integration method and polynomial type
6432 MString dgIntegrationMethod = m_sbpMode ? "DG_INTEGRATE_GAUSS_LOBATTO" : "DG_INTEGRATE_GAUS";
6433 dgIntegrationMethod =
6434 Context::getSolverProperty<MString>("dgIntegrationMethod", m_solverId, AT_, &dgIntegrationMethod);
6435 MString dgPolynomialType = "DG_POLY_LEGENDRE";
6436 dgPolynomialType = Context::getSolverProperty<MString>("dgPolynomialType", m_solverId, AT_, &dgPolynomialType);
6437
6438 // Add integration method & polynomial type to grid file as attributes
6439 parallelIo.setAttribute(dgIntegrationMethod, "dgIntegrationMethod", "polyDegs");
6440 parallelIo.setAttribute(dgPolynomialType, "dgPolynomialType", "polyDegs");
6441
6442 // Add arrays to file
6443 for(MInt i = 0; i < noVars; i++) {
6444 const MString name = "variables" + to_string(i);
6445 parallelIo.defineArray(PIO_FLOAT, name, globalNoNodes);
6446 parallelIo.setAttribute(varNames[i], "name", name);
6447 }
6448
6449 const MInt maxNoNodesXD = ipow(m_maxNoNodes1D, nDim);
6450 const MInt elementDataSize = noVars * maxNoNodesXD;
6451
6452 // Write data to disk
6453 // Create scratch space with polynomial degree and write it to file
6454 parallelIo.setOffset(grid().noInternalCells(), grid().domainOffset(domainId()));
6455 MUcharScratchSpace polyDegs(grid().noInternalCells(), FUN_, "polyDegs");
6456 fill_n(&polyDegs[0], grid().noInternalCells(), m_initPolyDeg);
6457 for(MInt elementId = 0; elementId < noElements; elementId++) {
6458 const MInt cellId = m_elements.cellId(elementId);
6459 polyDegs.p[cellId] = m_elements.polyDeg(elementId);
6460 }
6461 parallelIo.writeArray(polyDegs.begin(), "polyDegs");
6462
6463 // Create scratch space with noNodes and write it to file
6464 if(m_sbpMode) {
6465 parallelIo.setOffset(grid().noInternalCells(), grid().domainOffset(domainId()));
6466 MUcharScratchSpace noNodes1D(grid().noInternalCells(), FUN_, "noNodes1D");
6467 fill_n(&noNodes1D[0], grid().noInternalCells(), m_initNoNodes1D);
6468 for(MInt elementId = 0; elementId < noElements; elementId++) {
6469 const MInt cellId = m_elements.cellId(elementId);
6470 noNodes1D.p[cellId] = m_elements.noNodes1D(elementId);
6471 }
6472 parallelIo.writeArray(noNodes1D.begin(), "noNodes1D");
6473 }
6474
6475 parallelIo.setOffset(localNoNodes, nodesOffset);
6476 for(MInt i = 0; i < noVars; i++) {
6477 MFloatScratchSpace buffer(localNoNodes, AT_, "buffer");
6478 fill(buffer.begin(), buffer.end(), 0.0);
6479 for(MInt e = 0; e < noElements; e++) {
6480 MFloat* const b = &buffer[elementOffset[e]];
6481 const MInt dataOffset = e * elementDataSize + i;
6482 const MFloat* const v = &data[dataOffset];
6483 for(MInt n = 0; n < elementNodes[e]; n++) {
6484 b[n] = v[n * noVars];
6485 }
6486 }
6487 const MString name = "variables" + to_string(i);
6488 parallelIo.writeArray(&buffer[0], name);
6489 }
6490}
6491
6492
6500template <MInt nDim, class SysEqn>
6502 TRACE();
6503 CHECK_TIMERS_IO();
6504
6505 m_log << "Saving file with node variable data for restarting... ";
6506
6507 // Add solver number in case of multisolver execution
6508 stringstream ss;
6509 ss << "nodevars" << getIdentifier(g_multiSolverGrid, "_b", "");
6510
6511 // Assemble node variable names
6512 vector<MString> nodeVarNames(SysEqn::noNodeVars());
6513 for(MInt nodeVar = 0; nodeVar < SysEqn::noNodeVars(); nodeVar++) {
6514 nodeVarNames[nodeVar] = m_sysEqn.nodeVarNames(nodeVar);
6515 }
6516
6517 // Save node variables
6518 saveNodalData(ss.str(), SysEqn::noNodeVars(), nodeVarNames, &m_elements.nodeVars(0));
6519
6520 m_log << "done" << endl;
6521}
6522
6523
6531template <MInt nDim, class SysEqn>
6533 TRACE();
6534 CHECK_TIMERS_IO();
6535
6536 m_log << "Loading nodevars file... ";
6537
6538 // Add solver number in case of multisolver execution
6539 stringstream ss;
6540 ss << restartDir() << "nodevars" << getIdentifier(g_multiSolverGrid, "_b", "") << ParallelIo::fileExt();
6541
6542 // Create big data object to load data from disk
6543 using namespace parallel_io;
6544 ParallelIo parallelIo(ss.str(), PIO_READ, mpiComm());
6545
6546 // Determine data offset for each element in output buffer
6547 const MInt noElements = m_elements.size();
6548 MIntScratchSpace elementOffset(noElements, AT_, "elementOffset");
6549 for(MInt cellId = 0, offset = 0, elementId = 0; elementId < noElements; cellId++) {
6550 // If cellId is not the one of the current element (i.e. the current cell
6551 // does not have an element), add the default offset (based on initPolyDeg)
6552 if(cellId != m_elements.cellId(elementId)) {
6553 offset += ipow(m_initNoNodes1D, nDim);
6554 continue;
6555 }
6556
6557 // Otherwise use current offset for the current element
6558 elementOffset[elementId] = offset;
6559
6560 // Increase offset based on element polynomial degree
6561 const MInt noNodesXD = m_elements.noNodesXD(elementId);
6562 offset += noNodesXD;
6563
6564 // Set number of nodes to use later and proceed with next element
6565 elementId++;
6566 }
6567
6568 // Set data offsets and array sizes
6569 // Determine local data array size
6570 // array size = #nodes of elements + (#cells - #elements) * default cell size
6571 // (cells without elements use polyDeg = initPolyDeg)
6572 const MInt localNoNodes = m_noTotalNodesXD + (grid().noInternalCells() - noElements) * ipow(m_initNoNodes1D, nDim);
6573
6574 // Determine offset and global number of nodes
6575 ParallelIo::size_type nodesOffset, globalNoNodes;
6576 ParallelIo::calcOffset(localNoNodes, &nodesOffset, &globalNoNodes, mpiComm());
6577
6578 // Load data from disk
6579 parallelIo.setOffset(localNoNodes, nodesOffset);
6580 for(MInt i = 0; i < m_sysEqn.noNodeVars(); i++) {
6581 MFloatScratchSpace buffer(localNoNodes, AT_, "buffer");
6582 const MString name = "variables" + to_string(i);
6583 parallelIo.readArray(&buffer[0], name);
6584 for(MInt e = 0; e < noElements; e++) {
6585 const MFloat* const b = &buffer[elementOffset[e]];
6586 MFloat* const v = &m_elements.nodeVars(e) + i;
6587 const MInt noNodesXD = m_elements.noNodesXD(e);
6588 for(MInt n = 0; n < noNodesXD; n++) {
6589 v[n * m_sysEqn.noNodeVars()] = b[n];
6590 }
6591 }
6592 }
6593
6594 m_log << "done" << endl;
6595}
6596
6597
6609template <MInt nDim, class SysEqn>
6611 const MInt useNonSpecifiedRestartFile) {
6612 TRACE();
6613
6614 stringstream ss;
6615 if(useNonSpecifiedRestartFile) {
6616 ss << restartDir() << "restart_" << getIdentifier(g_multiSolverGrid, "b", "") << ParallelIo::fileExt();
6617 } else {
6618 ss << restartDir() << "restart_" << getIdentifier(g_multiSolverGrid, "b") << setw(8) << setfill('0') << timeStep
6619 << ParallelIo::fileExt();
6620 }
6621 const MString fileName = ss.str();
6622
6623 return fileName;
6624}
6625
6626
6627template <MInt nDim, class SysEqn>
6629 TRACE();
6630
6631 BC bc = m_boundaryConditionFactory.create(bcId);
6632
6633 return bc;
6634}
6635
6646template <MInt nDim, class SysEqn>
6648 TRACE();
6649 RECORD_TIMER_START(m_timers[Timers::TimeDeriv]);
6650
6651 // Reset the time derivative to zero
6652 resetRHS();
6653
6654 // Calculate solution on all surfaces and start exchange of surface data
6655 // In split MPI mode most of the time this was already done at the end of
6656 // timeStepRk(). If there are no open MPI requests it needs to be done here.
6657 if(!g_splitMpiComm || !m_mpiRecvRequestsOpen) {
6658 // Extrapolate the solution vector U from the element volume to all surfaces
6659 prolongToSurfaces();
6660
6661 // Apply forward mortar projection to h- and/or p-refined surfaces
6662 applyForwardProjection();
6663
6664 // Start exchanging surface data
6665 startMpiSurfaceExchange();
6666 }
6667
6668 // Calculate the volume integral and update dU/dt
6669 calcVolumeIntegral();
6670
6671 // Calculate the fluxes on the internal surfaces
6672 calcInnerSurfaceFlux();
6673
6674 // Exclude communication from domain load and add to idle time
6675 // Finish exchanging surface data
6676 finishMpiSurfaceExchange();
6677
6678 // Calculate the fluxes on the boundary surfaces
6679 // Note: this was previously called after calcVolumeIntegral(). It was moved
6680 // in order to make the radiation boundary conditions work in parallel without
6681 // any additional MPI communication.
6682 calcBoundarySurfaceFlux(tStage);
6683
6684 // Calculate the fluxes on the MPI surfaces
6685 calcMpiSurfaceFlux();
6686
6687 // Calculate the integrals for all surfaces and update dU/dt
6688 calcSurfaceIntegral();
6689
6690 // Apply Jacobian to dU/dt
6691 applyJacobian();
6692
6693 // Calculate source terms and add them to dU/dt
6694 calcSourceTerms(tStage);
6695
6696 // Add external (coupling) source terms
6697 applyExternalSourceTerms(tStage);
6698
6699 // Calculate sponge terms and add them to dU/dt
6700 if(useSponge()) {
6701 RECORD_TIMER_START(m_timers[Timers::Sponge]);
6702 sponge().calcSourceTerms();
6703 RECORD_TIMER_STOP(m_timers[Timers::Sponge]);
6704 }
6705
6706 RECORD_TIMER_STOP(m_timers[Timers::TimeDeriv]);
6707}
6708
6713template <MInt nDim, class SysEqn>
6715 TRACE();
6716 RECORD_TIMER_START(m_timers[Timers::Prolong]);
6717
6718 prolongToSurfaces(0, m_elements.size());
6719
6720 RECORD_TIMER_STOP(m_timers[Timers::Prolong]);
6721}
6722
6723
6732template <MInt nDim, class SysEqn>
6734 TRACE();
6735
6736 const MInt* polyDegs = &m_elements.polyDeg(0);
6737 const MInt* noNodes = &m_elements.noNodes1D(0);
6738 const MInt* surfaceIds = &m_elements.surfaceIds(0, 0);
6739
6740 using namespace dg::interpolation;
6741
6742 // IMPORTANT:
6743 // If anything in this method is changed, please check if
6744 // updateNodeVariables() needs to be changed as well, since it contains more
6745 // or less the same code.
6746
6747 if(m_dgIntegrationMethod == DG_INTEGRATE_GAUSS) {
6748#ifdef _OPENMP
6749#pragma omp parallel for
6750#endif
6751 // Loop over all elements in given range
6752 for(MInt elementId = begin; elementId < end; elementId++) {
6753 const MInt surfaceIdOffset = elementId * 2 * nDim;
6754 const MInt polyDeg = polyDegs[elementId];
6755 const MInt noNodes1D = noNodes[elementId];
6756 const DgInterpolation& interp = m_interpolation[polyDeg][noNodes1D];
6757
6758 // Extrapolate the solution to each surface on the faces
6759 for(MInt dir = 0; dir < 2 * nDim; dir++) {
6760 const MInt srfcId = surfaceIds[surfaceIdOffset + dir];
6761 const MInt side = 1 - dir % 2;
6762
6763 MFloat* src = &m_elements.variables(elementId);
6764 MFloat* dest = &m_surfaces.variables(srfcId, side);
6765
6766 prolongToFaceGauss<nDim, SysEqn::noVars()>(src, dir, noNodes1D, &interp.m_LFace[0][0], &interp.m_LFace[1][0],
6767 dest);
6768 }
6769 }
6770 } else if(m_dgIntegrationMethod == DG_INTEGRATE_GAUSS_LOBATTO) {
6771#ifdef _OPENMP
6772#pragma omp parallel for
6773#endif
6774 // Loop over all elements in given range
6775 for(MInt elementId = begin; elementId < end; elementId++) {
6776 const MInt surfaceIdOffset = elementId * 2 * nDim;
6777 const MInt noNodes1D = noNodes[elementId];
6778
6779 // Extrapolate the solution to each surface on the faces
6780 for(MInt dir = 0; dir < 2 * nDim; dir++) {
6781 const MInt srfcId = surfaceIds[surfaceIdOffset + dir];
6782 const MInt side = 1 - dir % 2;
6783
6784 MFloat* src = &m_elements.variables(elementId);
6785 MFloat* dest = &m_surfaces.variables(srfcId, side);
6786
6787 prolongToFaceGaussLobatto<nDim, SysEqn::noVars()>(src, dir, noNodes1D, dest);
6788 }
6789 }
6790 }
6791}
6792
6793
6804template <MInt nDim, class SysEqn>
6806 TRACE();
6807 RECORD_TIMER_START(m_timers[Timers::ForwardProjection]);
6808
6809 // IMPORTANT:
6810 // If anything in this method is changed, please check if
6811 // updateNodeVariables() needs to be changed as well, since it contains more
6812 // or less the same code.
6813
6814 const MInt* surfaceIds = &m_elements.surfaceIds(0, 0);
6815 const MInt noVars = SysEqn::noVars();
6816 const MInt maxNoNodes1D = m_maxNoNodes1D;
6817 const MInt maxNoNodes1D3 = (nDim == 3) ? maxNoNodes1D : 1;
6818 const MInt noHElements = m_helements.size();
6819 const MInt noDirs = 2 * nDim;
6820 const MInt noSurfs = 2 * (nDim - 1);
6821
6823 // Copy prolong results to h-refined surfaces (the prolong step stored its
6824 // results only in the first refined surface)
6826 if(noHElements > 0) {
6827#ifdef _OPENMP
6828#pragma omp parallel for
6829#endif
6830 for(MInt hElementId = 0; hElementId < noHElements; hElementId++) {
6831 const MInt coarseElementId = m_helements.elementId(hElementId);
6832 for(MInt dir = 0; dir < noDirs; dir++) {
6833 const MInt coarseSrfcId = m_elements.surfaceIds(coarseElementId, dir);
6834 for(MInt pos = 0; pos < noSurfs; pos++) {
6835 const MInt hSrfcId = m_helements.hrefSurfaceIds(hElementId, dir, pos);
6836 const MInt side = 1 - dir % 2;
6837
6838 // Skip if this surface does not exist
6839 if(hSrfcId == -1) {
6840 continue;
6841 }
6842
6843 // Copy values of prolong step to all remaining refined surfaces
6844 if(coarseSrfcId != hSrfcId) {
6845 const MInt noNodes1D = m_elements.noNodes1D(coarseElementId);
6846 const MInt noNodes1D3 = (nDim == 3) ? noNodes1D : 1;
6847 const MInt size = noNodes1D * noNodes1D3 * SysEqn::noVars();
6848
6849 // Copy values from prolong step to surface
6850 copy_n(&m_surfaces.variables(coarseSrfcId, side), size, &m_surfaces.variables(hSrfcId, side));
6851 }
6852 }
6853 }
6854 }
6855 }
6856
6858 // Apply mortar projection
6860 const MInt noElements = m_elements.size();
6861 MFloatTensor projected(maxNoNodes1D, maxNoNodes1D3, noVars);
6862
6863 // Apply projection to h- (and possibly p-)refined surfaces
6864 if(noHElements > 0) {
6865#ifdef _OPENMP
6866#pragma omp parallel for firstprivate(projected)
6867#endif
6868 for(MInt hElementId = 0; hElementId < noHElements; hElementId++) {
6869 for(MInt dir = 0; dir < noDirs; dir++) {
6870 for(MInt pos = 0; pos < noSurfs; pos++) {
6871 const MInt hSrfcId = m_helements.hrefSurfaceIds(hElementId, dir, pos);
6872 const MInt side = 1 - dir % 2;
6873
6874 // Skip if this surface does not exist
6875 if(hSrfcId == -1) {
6876 continue;
6877 }
6878
6879 const MInt surfaceNoNodes1D = m_surfaces.noNodes1D(hSrfcId);
6880 const MInt surfaceNoNodes1D3 = (nDim == 3) ? surfaceNoNodes1D : 1;
6881 const MInt size = surfaceNoNodes1D * surfaceNoNodes1D3 * noVars;
6882
6883 // Project the coarse side to the surface
6884 calcMortarProjection<dg::mortar::forward, SysEqn::noVars()>(
6885 hSrfcId, dir, &m_surfaces.variables(hSrfcId, side), &projected[0], m_elements, m_surfaces);
6886
6887 // Copy results of projection back to surface
6888 copy_n(&projected[0], size, &m_surfaces.variables(hSrfcId, side));
6889 }
6890 }
6891 }
6892 }
6893
6894 // Apply projection to pure p-refined surfaces
6895#ifdef _OPENMP
6896#pragma omp parallel for firstprivate(projected)
6897#endif
6898 for(MInt elementId = 0; elementId < noElements; elementId++) {
6899 const MInt surfaceIdOffset = elementId * 2 * nDim;
6900
6901 for(MInt dir = 0; dir < 2 * nDim; dir++) {
6902 // Define auxiliary variables for better readability
6903 const MInt srfcId = surfaceIds[surfaceIdOffset + dir];
6904
6905 // Skip boundary surfaces as they are *always* conforming
6906 if(srfcId < m_innerSurfacesOffset) {
6907 continue;
6908 }
6909
6910 const MInt surfacePolyDeg = m_surfaces.polyDeg(srfcId);
6911 const MInt elementPolyDeg = m_elements.polyDeg(elementId);
6912 const MInt side = 1 - dir % 2;
6913 const MInt surfaceNoNodes1D = m_surfaces.noNodes1D(srfcId);
6914 const MInt surfaceNoNodes1D3 = (nDim == 3) ? surfaceNoNodes1D : 1;
6915 const MInt size = surfaceNoNodes1D * surfaceNoNodes1D3 * noVars;
6916
6917 // Skip h-refined surfaces since they have been already projected
6918 if(m_surfaces.fineCellId(srfcId) != -1 && m_surfaces.fineCellId(srfcId) != m_elements.cellId(elementId)) {
6919 continue;
6920 }
6921
6922 // Calculate forward projection for lower polyDeg elements
6923 if(surfacePolyDeg > elementPolyDeg) {
6924 // Calculate forward projection
6925 calcMortarProjection<dg::mortar::forward, SysEqn::noVars()>(srfcId, dir, &m_surfaces.variables(srfcId, side),
6926 &projected[0], m_elements, m_surfaces);
6927
6928 // Copy results of projection back to surface
6929 copy_n(&projected[0], size, &m_surfaces.variables(srfcId, side));
6930 }
6931 }
6932 }
6933
6934 RECORD_TIMER_STOP(m_timers[Timers::ForwardProjection]);
6935}
6936
6937
6943template <MInt nDim, class SysEqn>
6945 TRACE();
6946 RECORD_TIMER_START(m_timers[Timers::VolInt]);
6947
6948 calcVolumeIntegral(m_elements.size(), m_elements, m_sysEqn);
6949
6950 RECORD_TIMER_STOP(m_timers[Timers::VolInt]);
6951}
6952
6953
6964template <MInt nDim, class SysEqn>
6966 TRACE();
6967 RECORD_TIMER_START(m_timers[Timers::Flux]);
6968 RECORD_TIMER_START(m_timers[Timers::FluxBndry]);
6969
6970 for(auto&& bc : m_boundaryConditions) {
6971 bc->apply(t);
6972 }
6973
6974 RECORD_TIMER_STOP(m_timers[Timers::FluxBndry]);
6975 RECORD_TIMER_STOP(m_timers[Timers::Flux]);
6976}
6977
6978
6986template <MInt nDim, class SysEqn>
6988 TRACE();
6989 RECORD_TIMER_START(m_timers[Timers::Flux]);
6990 RECORD_TIMER_START(m_timers[Timers::FluxInner]);
6991
6992 const MInt begin = m_innerSurfacesOffset;
6993 const MInt end = m_innerSurfacesOffset + m_noInnerSurfaces;
6994 calcRegularSurfaceFlux(begin, end, m_surfaces, m_sysEqn);
6995
6996 RECORD_TIMER_STOP(m_timers[Timers::FluxInner]);
6997 RECORD_TIMER_STOP(m_timers[Timers::Flux]);
6998}
6999
7000
7008template <MInt nDim, class SysEqn>
7010 TRACE();
7011 RECORD_TIMER_START(m_timers[Timers::Flux]);
7012 RECORD_TIMER_START(m_timers[Timers::FluxMPI]);
7013
7014 const MInt begin = m_mpiSurfacesOffset;
7015 const MInt end = m_mpiSurfacesOffset + m_noMpiSurfaces;
7016 calcRegularSurfaceFlux(begin, end, m_surfaces, m_sysEqn);
7017
7018 RECORD_TIMER_STOP(m_timers[Timers::FluxMPI]);
7019 RECORD_TIMER_STOP(m_timers[Timers::Flux]);
7020}
7021
7022
7028template <MInt nDim, class SysEqn>
7030 TRACE();
7031 RECORD_TIMER_START(m_timers[Timers::SurfInt]);
7032
7033 calcSurfaceIntegral(0, m_elements.size(), m_elements, m_surfaces, m_helements, m_helements.size());
7034
7035 RECORD_TIMER_STOP(m_timers[Timers::SurfInt]);
7036}
7037
7038
7050template <MInt nDim, class SysEqn>
7052 const MInt end,
7053 ElementCollector& elem,
7054 SurfaceCollector& surf,
7055 HElementCollector& helem,
7056 const MInt noHElements) {
7057 TRACE();
7058
7059 const MInt noVars = SysEqn::noVars();
7060 const MInt* surfaceIds = &elem.surfaceIds(0, 0);
7061
7066
7067#ifdef _OPENMP
7068#pragma omp parallel for
7069#endif
7070 // Loop over all elements and calculate surface integrals
7071 for(MInt elementId = begin; elementId < end; elementId++) {
7072 const MInt surfaceIdOffset = elementId * 2 * nDim;
7073
7074 for(MInt dir = 0; dir < 2 * nDim; dir++) {
7075 // Calculate auxiliary variables for better readability
7076 const MInt srfcId = surfaceIds[surfaceIdOffset + dir];
7077 const MInt srfcPolyDeg = surf.polyDeg(srfcId);
7078 const MInt srfcNodes1D = surf.noNodes1D(srfcId);
7079 const MInt srfcNodes1D3 = (nDim == 3) ? srfcNodes1D : 1;
7080 const MInt polyDeg = elem.polyDeg(elementId);
7081 const MInt noNodes1D = elem.noNodes1D(elementId);
7082 MFloatTensor f(&surf.flux(srfcId), srfcNodes1D, srfcNodes1D3, noVars);
7083
7084 const MInt side = 1 - dir % 2;
7085
7086 // Skip coarse surfaces they need to be handled separately
7087 if(surf.fineCellId(srfcId) != -1 && surf.fineCellId(srfcId) != elem.cellId(elementId)) {
7088 continue;
7089 }
7090
7091 // Pure p-refinement case (srfcPolyDeg is never small than polyDeg)
7092 if(srfcPolyDeg > polyDeg) {
7093 MFloatTensor fp(srfcNodes1D, srfcNodes1D3, noVars);
7094
7095 // Calculate reverse projection
7096 calcMortarProjection<dg::mortar::reverse, SysEqn::noVars()>(srfcId, dir, &f[0], &fp[0], elem, surf);
7097
7098 // Use projected flux for integration
7099 applySurfaceIntegral(&elem.rightHandSide(elementId), polyDeg, noNodes1D, srfcId, side, &fp[0], surf);
7100
7101 } else {
7102 // Use original flux for integration
7103 applySurfaceIntegral(&elem.rightHandSide(elementId), polyDeg, noNodes1D, srfcId, side, &f[0], surf);
7104 }
7105 }
7106 }
7107
7111 if(noHElements > 0) {
7112 const MInt noDirs = 2 * nDim;
7113 const MInt noSurfs = 2 * (nDim - 1);
7114
7115#ifdef _OPENMP
7116#pragma omp parallel for
7117#endif
7118 for(MInt hElementId = 0; hElementId < noHElements; hElementId++) {
7119 const MInt coarseElementId = helem.elementId(hElementId);
7120 const MInt polyDeg = elem.polyDeg(coarseElementId);
7121 const MInt noNodes1D = elem.noNodes1D(coarseElementId);
7122
7123 for(MInt dir = 0; dir < noDirs; dir++) {
7124 const MInt coarseSrfcId = elem.surfaceIds(coarseElementId, dir);
7125
7126 for(MInt pos = 0; pos < noSurfs; pos++) {
7127 const MInt hSrfcId = helem.hrefSurfaceIds(hElementId, dir, pos);
7128 const MInt side = 1 - dir % 2;
7129
7130 // Skip if this surface does not exist
7131 if(hSrfcId == -1) {
7132 continue;
7133 }
7134
7135 const MInt srfcNodes1D = surf.noNodes1D(hSrfcId);
7136 const MInt srfcNodes1D3 = (nDim == 3) ? srfcNodes1D : 1;
7137 MFloatTensor f(&surf.flux(hSrfcId), srfcNodes1D, srfcNodes1D3, noVars);
7138 MFloatTensor fp(srfcNodes1D, srfcNodes1D3, noVars);
7139
7140 // Calculate reverse projection
7141 calcMortarProjection<dg::mortar::reverse, SysEqn::noVars()>(hSrfcId, dir, &f[0], &fp[0], elem, surf);
7142
7143 // Use projected flux for integration
7144 applySurfaceIntegral(&elem.rightHandSide(coarseElementId), polyDeg, noNodes1D, coarseSrfcId, side, &fp[0],
7145 surf);
7146 }
7147 }
7148 }
7149 }
7150}
7151
7152
7168template <MInt nDim, class SysEqn>
7170 const MInt srfcId, const MInt side, const MFloat* flux,
7171 SurfaceCollector& surf) {
7172 // TRACE();
7173 const MInt noNodes1D3 = (nDim == 3) ? noNodes1D : 1;
7174 const MInt noVars = SysEqn::noVars();
7175 const MInt srfcNodes1D = surf.noNodes1D(srfcId);
7176 const MInt srfcNodes1D3 = (nDim == 3) ? srfcNodes1D : 1;
7177
7178 const MFloatTensor f(const_cast<MFloat*>(flux), srfcNodes1D, srfcNodes1D3, noVars);
7179
7180 // Calculate surface integral
7181 MFloatTensor ut(rhs, noNodes1D, noNodes1D, noNodes1D3, noVars);
7182 const MInt dirId = surf.orientation(srfcId);
7183 const MInt index = (side == 0) ? (noNodes1D - 1) : 0;
7184 const MInt sign = (side == 0) ? 1 : -1;
7185 const DgInterpolation& interp = m_interpolation[polyDeg][noNodes1D];
7186
7187
7188 if(m_dgIntegrationMethod == DG_INTEGRATE_GAUSS) {
7189 // Use different loops depending on the surface orientation
7190 switch(dirId) {
7191 case 0:
7192 for(MInt i = 0; i < noNodes1D; i++) {
7193 for(MInt j = 0; j < noNodes1D; j++) {
7194 for(MInt k = 0; k < noNodes1D3; k++) {
7195 for(MInt n = 0; n < noVars; n++) {
7196 ut(i, j, k, n) += sign * f(j, k, n) * interp.m_LhatFace[1 - side][i];
7197 }
7198 }
7199 }
7200 }
7201 break;
7202
7203 case 1:
7204 for(MInt i = 0; i < noNodes1D; i++) {
7205 for(MInt j = 0; j < noNodes1D; j++) {
7206 for(MInt k = 0; k < noNodes1D3; k++) {
7207 for(MInt n = 0; n < noVars; n++) {
7208 ut(i, j, k, n) += sign * f(i, k, n) * interp.m_LhatFace[1 - side][j];
7209 }
7210 }
7211 }
7212 }
7213 break;
7214
7215 case 2:
7216 for(MInt i = 0; i < noNodes1D; i++) {
7217 for(MInt j = 0; j < noNodes1D; j++) {
7218 for(MInt k = 0; k < noNodes1D3; k++) {
7219 for(MInt n = 0; n < noVars; n++) {
7220 ut(i, j, k, n) += sign * f(i, j, n) * interp.m_LhatFace[1 - side][k];
7221 }
7222 }
7223 }
7224 }
7225 break;
7226 default:
7227 mTerm(1, AT_, "Bad direction id");
7228 }
7229 } else if(m_dgIntegrationMethod == DG_INTEGRATE_GAUSS_LOBATTO) {
7230 // Use different loops depending on the surface orientation
7231 switch(dirId) {
7232 case 0:
7233 for(MInt j = 0; j < noNodes1D; j++) {
7234 for(MInt k = 0; k < noNodes1D3; k++) {
7235 for(MInt n = 0; n < noVars; n++) {
7236 ut(index, j, k, n) += sign * f(j, k, n) * interp.m_LhatFace[1 - side][index];
7237 }
7238 }
7239 }
7240 break;
7241
7242 case 1:
7243 for(MInt i = 0; i < noNodes1D; i++) {
7244 for(MInt k = 0; k < noNodes1D3; k++) {
7245 for(MInt n = 0; n < noVars; n++) {
7246 ut(i, index, k, n) += sign * f(i, k, n) * interp.m_LhatFace[1 - side][index];
7247 }
7248 }
7249 }
7250 break;
7251
7252 case 2:
7253 for(MInt i = 0; i < noNodes1D; i++) {
7254 for(MInt j = 0; j < noNodes1D3; j++) {
7255 for(MInt n = 0; n < noVars; n++) {
7256 ut(i, j, index, n) += sign * f(i, j, n) * interp.m_LhatFace[1 - side][index];
7257 }
7258 }
7259 }
7260 break;
7261
7262 default:
7263 mTerm(1, AT_, "Bad direction id");
7264 }
7265 }
7266}
7267
7268
7273template <MInt nDim, class SysEqn>
7275 TRACE();
7276 RECORD_TIMER_START(m_timers[Timers::Jacobian]);
7277
7278 applyJacobian(m_elements.size(), m_elements);
7279
7280 RECORD_TIMER_STOP(m_timers[Timers::Jacobian]);
7281}
7282
7283
7293template <MInt nDim, class SysEqn>
7295 TRACE();
7296
7297 MFloat* const invJacobians = &elem.invJacobian(0);
7298
7299#ifdef _OPENMP
7300#pragma omp parallel for
7301#endif
7302 for(MInt elementId = 0; elementId < noElements; elementId++) {
7303 const MInt dataBlockSize = elem.noNodesXD(elementId) * SysEqn::noVars();
7304 const MFloat invJacobian = invJacobians[elementId];
7305
7306 MFloat* const rhs = &elem.rightHandSide(elementId);
7307 for(MInt dataId = 0; dataId < dataBlockSize; dataId++) {
7308 rhs[dataId] *= -invJacobian;
7309 }
7310 }
7311}
7312
7313
7319template <MInt nDim, class SysEqn>
7321 TRACE();
7322 RECORD_TIMER_START(m_timers[Timers::Sources]);
7323
7324 calcSourceTerms(t, m_elements.size(), m_elements, m_sysEqn);
7325
7326 RECORD_TIMER_STOP(m_timers[Timers::Sources]);
7327}
7328
7329
7331template <MInt nDim, class SysEqn>
7333 TRACE();
7334 RECORD_TIMER_START(m_timers[Timers::ExternalSources]);
7335
7336 const MInt* noNodes1D = &m_elements.noNodes1D(0);
7337
7338 // Source term ramp up factor (linear in time)
7339 const MFloat rampUpFactor =
7340 (m_useSourceRampUp) ? maia::filter::slope::linear(m_startTime, m_startTime + m_sourceRampUpTime, time) : 1.0;
7341
7342#ifdef _OPENMP
7343#pragma omp parallel for
7344#endif
7345 for(MInt elementId = 0; elementId < m_elements.size(); elementId++) {
7346 const MInt dataBlockSize = ipow(noNodes1D[elementId], nDim) * SysEqn::noVars();
7347 MFloat* const rhs = &m_elements.rightHandSide(elementId);
7348 const MFloat* const sources = &m_elements.externalSource(elementId);
7349
7350 for(MInt dataId = 0; dataId < dataBlockSize; dataId++) {
7351 rhs[dataId] += rampUpFactor * sources[dataId];
7352 }
7353 }
7354
7355 RECORD_TIMER_STOP(m_timers[Timers::ExternalSources]);
7356}
7357
7358
7365template <MInt nDim, class SysEqn>
7367 return (domainId() == 0);
7368}
7369
7370
7378template <MInt nDim, class SysEqn>
7380 TRACE();
7381
7382 // IMPORTANT:
7383 // If anything in this method is changed, please check if
7384 // updateNodeVariables() needs to be changed as well, since it contains more
7385 // or less the same code.
7386
7387 RECORD_TIMER_START(m_timers[Timers::SurfExchange]);
7388 RECORD_TIMER_START(m_timers[Timers::Accumulated]);
7389 RECORD_TIMER_START(m_timers[Timers::MPI]);
7390
7391 // Start receiving
7392 RECORD_TIMER_START(m_timers[Timers::MPIComm]);
7393 RECORD_TIMER_START(m_timers[Timers::SurfExchangeComm]);
7394 RECORD_TIMER_START(m_timers[Timers::SECommRecv]);
7395 if(!m_mpiRecvRequestsOpen && m_noExchangeNghbrDomains > 0) {
7396 MPI_Startall(m_noExchangeNghbrDomains, &m_recvRequests[0], AT_);
7397 m_mpiRecvRequestsOpen = true;
7398 }
7399 RECORD_TIMER_STOP(m_timers[Timers::SECommRecv]);
7400 RECORD_TIMER_STOP(m_timers[Timers::SurfExchangeComm]);
7401 RECORD_TIMER_STOP(m_timers[Timers::MPIComm]);
7402
7403 // Finish previous sending
7404 RECORD_TIMER_START(m_timers[Timers::MPIWait]);
7405 RECORD_TIMER_START(m_timers[Timers::SurfExchangeWait]);
7406 RECORD_TIMER_START(m_timers[Timers::SEWaitSend]);
7407 if(m_mpiSendRequestsOpen && m_noExchangeNghbrDomains > 0) {
7408 MPI_Waitall(m_noExchangeNghbrDomains, &m_sendRequests[0], MPI_STATUSES_IGNORE, AT_);
7409 m_mpiSendRequestsOpen = false;
7410 }
7411 RECORD_TIMER_STOP(m_timers[Timers::SEWaitSend]);
7412 RECORD_TIMER_STOP(m_timers[Timers::SurfExchangeWait]);
7413 RECORD_TIMER_STOP(m_timers[Timers::MPIWait]);
7414
7415 // Pack send buffers
7416#ifdef DG_USE_MPI_BUFFERS
7417 RECORD_TIMER_START(m_timers[Timers::MPICopy]);
7418 RECORD_TIMER_START(m_timers[Timers::SurfExchangeCopy]);
7419 RECORD_TIMER_START(m_timers[Timers::SECopySend]);
7420
7421 // Use max. polynomial degree to account for p-refined surfaces
7422 const MInt dataBlockSize = ipow(m_maxNoNodes1D, nDim - 1) * SysEqn::noVars();
7423
7424 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
7425 MInt size = 0;
7426 for(vector<MInt>::size_type j = 0; j < m_mpiSurfaces[i].size(); j++) {
7427 const MInt srfcId = m_mpiSurfaces[i][j];
7428 const MInt sideId = m_surfaces.internalSideId(srfcId);
7429
7430 // Copy state data
7431 const MFloat* data = &m_surfaces.variables(srfcId, sideId);
7432 copy(data, data + dataBlockSize, &m_sendBuffers[i][size]);
7433 size += dataBlockSize;
7434 }
7435 ASSERT(size == static_cast<MInt>(m_sendBuffers[i].size()), "Data size does not match buffer size.");
7436 }
7437 RECORD_TIMER_STOP(m_timers[Timers::SECopySend]);
7438 RECORD_TIMER_STOP(m_timers[Timers::SurfExchangeCopy]);
7439 RECORD_TIMER_STOP(m_timers[Timers::MPICopy]);
7440#endif
7441
7442 // Start sending
7443 RECORD_TIMER_START(m_timers[Timers::MPIComm]);
7444 RECORD_TIMER_START(m_timers[Timers::SurfExchangeComm]);
7445 RECORD_TIMER_START(m_timers[Timers::SECommSend]);
7446 if(m_noExchangeNghbrDomains > 0) {
7447 MPI_Startall(m_noExchangeNghbrDomains, &m_sendRequests[0], AT_);
7448 }
7449 RECORD_TIMER_STOP(m_timers[Timers::SECommSend]);
7450 RECORD_TIMER_STOP(m_timers[Timers::SurfExchangeComm]);
7451 RECORD_TIMER_STOP(m_timers[Timers::MPIComm]);
7452
7453 m_mpiSendRequestsOpen = true;
7454
7455 RECORD_TIMER_STOP(m_timers[Timers::MPI]);
7456 RECORD_TIMER_STOP(m_timers[Timers::Accumulated]);
7457 RECORD_TIMER_STOP(m_timers[Timers::SurfExchange]);
7458}
7459
7460
7468template <MInt nDim, class SysEqn>
7470 TRACE();
7471 RECORD_TIMER_START(m_timers[Timers::SurfExchange]);
7472 RECORD_TIMER_START(m_timers[Timers::Accumulated]);
7473 RECORD_TIMER_START(m_timers[Timers::MPI]);
7474
7475 // IMPORTANT:
7476 // If anything in this method is changed, please check if
7477 // updateNodeVariables() needs to be changed as well, since it contains more
7478 // or less the same code.
7479
7480 if(noDomains() > 1 && (!m_mpiRecvRequestsOpen || !m_mpiSendRequestsOpen)) {
7481 TERMM(1, "MPI requests are not open: receive=" + std::to_string(m_mpiRecvRequestsOpen)
7482 + ", send=" + std::to_string(m_mpiSendRequestsOpen));
7483 }
7484
7485#ifdef DG_USE_MPI_WAITSOME
7486 TERMM(1, "This code is untested and will not work with p-refinement.");
7487
7488 // Init status array and counter
7489 vector<MInt> indices(m_noExchangeNghbrDomains);
7490 MInt noFinished;
7491
7492 // Start with receiving data and unpack the buffers as they are filled
7493 // with incoming data
7494 RECORD_TIMER_START(m_timers[Timers::MPIWait]);
7495 RECORD_TIMER_START(m_timers[Timers::SurfExchangeWait]);
7496 RECORD_TIMER_START(m_timers[Timers::SEWaitRecv]);
7497 MPI_Waitsome(m_noExchangeNghbrDomains, &m_recvRequests[0], &noFinished, &indices[0], MPI_STATUSES_IGNORE, AT_);
7498 while(noFinished != MPI_UNDEFINED) {
7499 for(MInt i = 0; i < noFinished; i++) {
7500 const MInt index = indices[i];
7501 MInt size = 0;
7502 for(size_t j = 0; j < m_mpiSurfaces[index].size(); j++) {
7503 const MInt srfcId = m_mpiSurfaces[index][j];
7504 const MInt noNodes1D = m_surfaces.noNodes1D(srfcId);
7505 const MInt noNodesXD = ipow(noNodes1D, nDim - 1);
7506 const MInt sideId = 1 - m_surfaces.internalSideId(srfcId);
7507
7508 MFloat* data = m_surfaces.variables(srfcId, sideId);
7509 const MInt dataBlockSize = noNodesXD * SysEqn::noVars();
7510 copy_n(&m_recvBuffers[index][size], dataBlockSize, data);
7511 size += dataBlockSize;
7512 }
7513 ASSERT(size == static_cast<MInt>(m_recvBuffers[index].size()), "Data size does not match buffer size.");
7514 }
7515
7516 // Wait for next batch
7517 MPI_Waitsome(m_noExchangeNghbrDomains, &m_recvRequests[0], &noFinished, &indices[0], MPI_STATUSES_IGNORE, AT_);
7518 }
7519 RECORD_TIMER_STOP(m_timers[Timers::SEWaitRecv]);
7520 RECORD_TIMER_STOP(m_timers[Timers::SurfExchangeWait]);
7521 RECORD_TIMER_STOP(m_timers[Timers::MPIWait]);
7522#else
7523
7524 // Finish receiving
7525 RECORD_TIMER_START(m_timers[Timers::MPIWait]);
7526 RECORD_TIMER_START(m_timers[Timers::SurfExchangeWait]);
7527 RECORD_TIMER_START(m_timers[Timers::SEWaitRecv]);
7528 if(m_noExchangeNghbrDomains > 0) {
7529 MPI_Waitall(m_noExchangeNghbrDomains, &m_recvRequests[0], MPI_STATUSES_IGNORE, AT_);
7530 }
7531 RECORD_TIMER_STOP(m_timers[Timers::SEWaitRecv]);
7532 RECORD_TIMER_STOP(m_timers[Timers::SurfExchangeWait]);
7533 RECORD_TIMER_STOP(m_timers[Timers::MPIWait]);
7534
7535 // Unpack receive buffers
7536#ifdef DG_USE_MPI_BUFFERS
7537 RECORD_TIMER_START(m_timers[Timers::MPICopy]);
7538 RECORD_TIMER_START(m_timers[Timers::SurfExchangeCopy]);
7539 RECORD_TIMER_START(m_timers[Timers::SECopyRecv]);
7540
7541 // Use max. polynomial degree to account for p-refined surfaces
7542 const MInt dataBlockSize = ipow(m_maxNoNodes1D, nDim - 1) * SysEqn::noVars();
7543
7544 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
7545 MInt size = 0;
7546 for(vector<MInt>::size_type j = 0; j < m_mpiSurfaces[i].size(); j++) {
7547 // Here the surface id from m_mpiRecvSurfaces is used to handle the case of communication
7548 // of a domain with itself due to periodically connected boundaries
7549 const MInt srfcId = m_mpiRecvSurfaces[i][j];
7550
7551
7552 // Use max. polynomial degree to account for p-refined surfaces
7553 const MInt sideId = 1 - m_surfaces.internalSideId(srfcId);
7554 MFloat* data = &m_surfaces.variables(srfcId, sideId);
7555 copy_n(&m_recvBuffers[i][size], dataBlockSize, data);
7556 size += dataBlockSize;
7557 }
7558 ASSERT(size == static_cast<MInt>(m_recvBuffers[i].size()), "Data size does not match buffer size.");
7559 }
7560 RECORD_TIMER_STOP(m_timers[Timers::SECopyRecv]);
7561 RECORD_TIMER_STOP(m_timers[Timers::SurfExchangeCopy]);
7562 RECORD_TIMER_STOP(m_timers[Timers::MPICopy]);
7563#endif // DG_USE_MPI_BUFFERS
7564#endif // DG_USE_MPI_WAITSOME
7565
7566 m_mpiRecvRequestsOpen = false;
7567
7568 // Start new receive requests
7569 if(m_noExchangeNghbrDomains > 0) {
7570 MPI_Startall(m_noExchangeNghbrDomains, &m_recvRequests[0], AT_);
7571 m_mpiRecvRequestsOpen = true;
7572 }
7573
7574 RECORD_TIMER_STOP(m_timers[Timers::MPI]);
7575 RECORD_TIMER_STOP(m_timers[Timers::Accumulated]);
7576 RECORD_TIMER_STOP(m_timers[Timers::SurfExchange]);
7577}
7578
7579
7586template <MInt nDim, class SysEqn>
7588 TRACE();
7589 RECORD_TIMER_START(m_timers[Timers::CalcTimeStep]);
7590
7591 MFloat minDt = numeric_limits<MFloat>::infinity();
7592
7593 // First check if external coupling overrides the time step calculation
7594 const MBool forcedTimeStep = (m_externalDt > 0.0);
7595
7596 // Time step calculation (if forced to check that the external dt is small enough)
7597 {
7598 // Calculate time step from system of equations class
7599 const MInt noElements = m_elements.size();
7600 const MInt* noNodes1D = &m_elements.noNodes1D(0);
7601 const MFloat* invJacobians = &m_elements.invJacobian(0);
7602
7603#ifdef _OPENMP
7604#pragma omp parallel for reduction(min : minDt)
7605#endif
7606 for(MInt elementId = 0; elementId < noElements; elementId++) {
7607 MFloat ts = m_sysEqn.getTimeStep(&m_elements.nodeVars(elementId), &m_elements.variables(elementId),
7608 noNodes1D[elementId], invJacobians[elementId], m_sbpMode);
7609 minDt = min(minDt, ts);
7610 }
7611 }
7612
7613 if(forcedTimeStep) {
7614 if(m_externalDt > minDt) {
7615 cerr << " WARNING: external dt larger than computed time step size on global domain " << globalDomainId() << "("
7616 << m_externalDt << " > " << minDt << ")" << std::endl;
7617 }
7618 // Calculate global minimum computed time step
7619 MPI_Allreduce(MPI_IN_PLACE, &minDt, 1, type_traits<MFloat>::mpiType(), MPI_MIN, mpiComm(), AT_, "MPI_IN_PLACE",
7620 "minDt");
7621 m_log << "DG-Solver #" << solverId() << " using external time step size: " << m_externalDt
7622 << " (computed global minimum: " << minDt << ")" << std::endl;
7623 minDt = m_externalDt;
7624 }
7625
7626 // Check for NaN in time step
7627 if(std::isnan(minDt)) {
7628 TERMM(1,
7629 "Time step is NaN at time step " + to_string(m_timeStep) + " on global domain "
7630 + to_string(globalDomainId()));
7631 }
7632
7633 // Check negative time step
7634 if(minDt < 0.0) {
7635 TERMM(1,
7636 "Time step is less than zero at time step " + to_string(m_timeStep) + " on global domain "
7637 + to_string(globalDomainId()));
7638 }
7639
7640 // Check ridiculuously small time step that indicates *most probably* an error
7641 // Note: the threshold here was arbitrarily chosen, but seemed "low enough"
7642 if(minDt < 1.0e-100) {
7643 TERMM(1,
7644 "Time step is less than 1.0e-100 at time step " + to_string(m_timeStep) + " on global domain "
7645 + to_string(globalDomainId()));
7646 }
7647
7648 // Calculate global minimum time step
7649 MPI_Allreduce(MPI_IN_PLACE, &minDt, 1, type_traits<MFloat>::mpiType(), MPI_MIN, mpiComm(), AT_, "MPI_IN_PLACE",
7650 "minDt");
7651
7652 RECORD_TIMER_STOP(m_timers[Timers::CalcTimeStep]);
7653
7654 return minDt;
7655}
7656
7657
7674template <MInt nDim, class SysEqn>
7675void DgCartesianSolver<nDim, SysEqn>::timeStepRk(const MFloat t, const MFloat dt, const MInt substep) {
7676 TRACE();
7677
7678 RECORD_TIMER_START(m_timers[Timers::RungeKuttaStep]);
7679
7680 // Save total data length
7681 const MInt totalSize = m_internalDataSize;
7682
7683 // General Runge-Kutta time step calculation
7684 for(MInt s = 0; s < m_noRkStages; s++) {
7685 // Perform only the given single Runge-Kutta substep if specified
7686 if(substep != -1 && s != substep) {
7687 continue;
7688 }
7689
7690 calcDgTimeDerivative(t, t + m_timeIntegrationCoefficientsC[s] * dt);
7691
7692 RECORD_TIMER_START(m_timers[Timers::TimeInt]);
7693 subTimeStepRk(dt, s, totalSize, &m_elements.rightHandSide(0), &m_elements.variables(0),
7694 &m_elements.timeIntStorage(0));
7695 RECORD_TIMER_STOP(m_timers[Timers::TimeInt]);
7696
7697
7698 // Determine if this is the final Runge-Kutta stage before an adaptation
7699 // time step (error estimates are calculated with the current solution on
7700 // all surfaces, so prevent overwriting the surface states if split MPI
7701 // communication is active)
7702 const MBool lastRkStage = (s == (m_noRkStages - 1));
7703 const MBool adaptationTimeStep = (lastRkStage && isAdaptationTimeStep(m_timeStep + 1));
7704
7705 // If multiphysics-optimized parallelization is used, prolong and start
7706 // tranmitting
7707 if(g_splitMpiComm && !adaptationTimeStep) {
7708 RECORD_TIMER_START(m_timers[Timers::TimeDeriv]);
7709 prolongToSurfaces();
7710 applyForwardProjection();
7711 startMpiSurfaceExchange();
7712 RECORD_TIMER_STOP(m_timers[Timers::TimeDeriv]);
7713 }
7714 }
7715
7716
7717 RECORD_TIMER_STOP(m_timers[Timers::RungeKuttaStep]);
7718}
7719
7720
7732template <MInt nDim, class SysEqn>
7734 const MInt stage,
7735 const MInt totalSize,
7736 const MFloat* const rhs,
7737 MFloat* const variables,
7738 MFloat* const timeIntStorage) {
7739 TRACE();
7740
7741 // Get pointers for a more concise code
7742 MFloat* const p = variables;
7743 MFloat* const k = timeIntStorage;
7744
7745 // Calculate auxiliary variables to save on operations
7746 MFloat bDt = -1.0;
7747 bDt = m_timeIntegrationCoefficientsB[stage] * dt;
7748
7749 // Stage 0
7750 if(stage == 0) {
7751#ifdef _OPENMP
7752#pragma omp parallel for
7753#endif
7754 for(MInt i = 0; i < totalSize; i++) {
7755 k[i] = rhs[i];
7756 p[i] += k[i] * bDt;
7757 }
7758 } else {
7759 // Stage 1-End
7760#ifdef _OPENMP
7761#pragma omp parallel for
7762#endif
7763 for(MInt i = 0; i < totalSize; i++) {
7764 k[i] = rhs[i] - k[i] * m_timeIntegrationCoefficientsA[stage];
7765 p[i] += k[i] * bDt;
7766 }
7767 }
7768}
7769
7770
7780template <MInt nDim, class SysEqn>
7782 MInt timeStep, MFloat dt) {
7783 TRACE();
7784 RECORD_TIMER_START(m_timers[Timers::Accumulated]);
7785 RECORD_TIMER_START(m_timers[Timers::AnalyzeTimeStep]);
7786 // Calculate error norms only if enabled in properties
7787 vector<MFloat> L2Error(m_sysEqn.noVars());
7788 vector<MFloat> LInfError(m_sysEqn.noVars());
7789 vector<MFloat> L2ErrLocal(m_sysEqn.noVars());
7790 vector<MFloat> LInfErrLocal(m_sysEqn.noVars());
7791 if(m_calcErrorNorms) {
7792 calcErrorNorms(t, L2Error, LInfError, L2ErrLocal, LInfErrLocal);
7793 // If any of the error measures are NaN, write a solution file before the
7794 // simulation is aborted
7795 if(any_of(L2Error.begin(), L2Error.end(), [](const MFloat e) { return std::isnan(e); })
7796 || any_of(LInfError.begin(), LInfError.end(), [](const MFloat e) { return std::isnan(e); })) {
7797 saveSolutionFile("nan_" + to_string(timeStep));
7798 }
7799
7800 // Check error norms for NaN and abort if found
7801 for(MInt i = 0; i < m_sysEqn.noVars(); i++) {
7802 if(std::isnan(L2ErrLocal[i])) {
7803 TERMM(1, "L^2 error for variable '" + m_sysEqn.consVarNames(i) + "' is NaN at time step " + to_string(timeStep)
7804 + " on global domain " + to_string(globalDomainId()));
7805 }
7806 if(std::isnan(LInfErrLocal[i])) {
7807 TERMM(1, "L^inf error for variable '" + m_sysEqn.consVarNames(i) + "' is NaN at time step "
7808 + to_string(timeStep) + " on global domain " + to_string(globalDomainId()));
7809 }
7810 }
7811 }
7812
7813 const MInt precision = m_noErrorDigits;
7814 const MInt fieldwith = precision + 6;
7815
7816 if(isMpiRoot()) {
7817 stringstream log;
7818
7819 log << endl;
7820 log << "----------------------------------------"
7821 << "----------------------------------------" << endl;
7822 log << " Solver " << solverId() << " running '" << m_sysEqn.sysEqnName() << "' with N = " << m_initPolyDeg
7823 << " and maxLevel = " << grid().maxLevel() << endl;
7824 log << "----------------------------------------"
7825 << "----------------------------------------" << endl;
7826 log << " No. timesteps: " << timeStep << endl;
7827 log << " dt: " << scientific << dt << endl;
7828 log << " Run time: " << runTimeTotal << " s" << endl;
7829 log << " Time/DOF/step: " << runTimeRelative << " s" << endl;
7830
7831 if(m_calcErrorNorms) {
7832 streamsize ss = log.precision();
7833 log << setprecision(precision);
7834 log << " Variable: ";
7835 for(MInt i = 0; i < m_sysEqn.noVars(); i++) {
7836 log << " " << left << setw(fieldwith) << setfill(' ') << m_sysEqn.consVarNames(i);
7837 }
7838 log << right << endl;
7839 log << " L^2 error: ";
7840 for(MInt i = 0; i < m_sysEqn.noVars(); i++) {
7841 log << " " << L2Error[i];
7842 }
7843 log << endl;
7844 log << " L^inf error: ";
7845 for(MInt i = 0; i < m_sysEqn.noVars(); i++) {
7846 log << " " << LInfError[i];
7847 }
7848 log << endl;
7849 log << setprecision(ss);
7850 }
7851
7852 log << "----------------------------------------"
7853 << "----------------------------------------" << endl;
7854 log << " Simulation time: " << t << endl;
7855 log << "----------------------------------------"
7856 << "----------------------------------------" << endl;
7857
7858 m_log << log.str() << endl;
7859 cout << log.str() << endl;
7860
7861 // Determine if this is the last time step and reset time step if necessary
7862 MBool finalTimeStep = false;
7863 if(m_finalTime - m_time - m_dt < 1.0E-10) {
7864 finalTimeStep = true;
7865 } else if(m_timeStep == m_timeSteps) {
7866 finalTimeStep = true;
7867 }
7868
7869 if(m_calcErrorNorms && isMpiRoot() && finalTimeStep) {
7870 std::ofstream eocStream;
7871 eocStream.open("EOCout.csv");
7872 eocStream << m_statGlobalNoActiveDOFs << "," << L2Error[0] << "," << LInfError[0];
7873 eocStream.close();
7874
7875 std::ofstream perfStream;
7876 perfStream.open("TimerOut.csv");
7877 perfStream << m_statGlobalNoActiveCells << "," << m_timeStep << "," << runTimeTotal << "," << runTimeRelative;
7878 perfStream.close();
7879 }
7880 }
7881
7882 // write local errors
7883 if(m_calcErrorNorms) {
7884 stringstream logLocal;
7885 logLocal << setprecision(precision) << scientific;
7886 logLocal << endl << " Variable: ";
7887 for(MInt i = 0; i < m_sysEqn.noVars(); i++) {
7888 logLocal << " " << left << setw(fieldwith) << setfill(' ') << m_sysEqn.consVarNames(i);
7889 }
7890 logLocal << right << endl;
7891 logLocal << " Local L^2 error: ";
7892 for(MInt i = 0; i < m_sysEqn.noVars(); i++) {
7893 logLocal << " " << L2ErrLocal[i];
7894 }
7895 logLocal << endl;
7896 logLocal << " Local L^inf error:";
7897 for(MInt i = 0; i < m_sysEqn.noVars(); i++) {
7898 logLocal << " " << LInfErrLocal[i];
7899 }
7900 logLocal << endl;
7901
7902 m_log << logLocal.str() << endl;
7903 }
7904
7905 RECORD_TIMER_STOP(m_timers[Timers::AnalyzeTimeStep]);
7906 RECORD_TIMER_STOP(m_timers[Timers::Accumulated]);
7907}
7908
7909
7924template <MInt nDim, class SysEqn>
7926 vector<MFloat>& L2Error,
7927 vector<MFloat>& LInfError,
7928 vector<MFloat>& L2ErrLocal,
7929 vector<MFloat>& LInfErrLocal) {
7930 TRACE();
7931 const MInt noVars = m_sysEqn.noVars();
7932 L2Error.assign(noVars, F0);
7933 LInfError.assign(noVars, F0);
7934 L2ErrLocal.assign(noVars, F0);
7935 LInfErrLocal.assign(noVars, F0);
7936
7937 // Allocate space for temporary values
7938 MFloatScratchSpace uExact(noVars, FUN_, "uExact");
7939 const MInt noNodesAnalysis1D = m_noAnalysisNodes;
7940 const MInt noNodesAnalysis1D3 = (nDim == 3) ? noNodesAnalysis1D : 1;
7941 MFloatTensor u(noNodesAnalysis1D, noNodesAnalysis1D, noNodesAnalysis1D3, noVars);
7942 MFloatTensor x(noNodesAnalysis1D, noNodesAnalysis1D, noNodesAnalysis1D3, nDim);
7943 // Set node vars to minimum 1 to avoid errors in Tensor
7944 // TODO labels:DG Check if this is really sensible
7945 MFloatTensor nodeVars(noNodesAnalysis1D, noNodesAnalysis1D, noNodesAnalysis1D3, max(SysEqn::noNodeVars(), 1));
7946
7947 // Loop over internal elements and calculate local errors
7948 const MInt noElements = m_elements.size();
7949 for(MInt elementId = 0; elementId < noElements; elementId++) {
7950 // Interpolate solution from regular nodes to analysis nodes
7951 const MInt polyDegElement = m_elements.polyDeg(elementId);
7952 const MInt noNodesElement = m_elements.noNodes1D(elementId);
7953 auto vdm = m_vdmAnalysis[polyDegElement][noNodesElement];
7954 dg::interpolation::interpolateNodes<nDim>(&m_elements.variables(elementId), &vdm[0], noNodesElement,
7955 m_noAnalysisNodes, noVars, &u[0]);
7956
7957 // Interpolation node locations from regular nodes to analysis nodes
7958 dg::interpolation::interpolateNodes<nDim>(&m_elements.nodeCoordinates(elementId), &vdm[0], noNodesElement,
7959 m_noAnalysisNodes, nDim, &x[0]);
7960
7961 // Calculate error norms
7962 const MFloat jacobian = pow(F1 / m_elements.invJacobian(elementId), nDim);
7963 for(MInt i = 0; i < noNodesAnalysis1D; i++) {
7964 for(MInt j = 0; j < noNodesAnalysis1D; j++) {
7965 for(MInt k = 0; k < noNodesAnalysis1D3; k++) {
7966 // TODO labels:DG Check if we need to initialize coupling quantities here as
7967 // well (instead of just interpolating)
7968 m_sysEqn.calcInitialCondition(t, &x(i, j, k, 0), &nodeVars(i, j, k, 0), &uExact[0]);
7969 for(MInt v = 0; v < noVars; v++) {
7970 const MFloat diff = uExact[v] - u(i, j, k, v);
7971 L2ErrLocal[v] += pow(diff, F2) * m_wVolumeAnalysis(i, j, k) * jacobian;
7972 LInfErrLocal[v] = max(LInfErrLocal[v], fabs(diff));
7973 }
7974 }
7975 }
7976 }
7977 }
7978
7979 // Calculate global errors
7980 RECORD_TIMER_START(m_timers[Timers::MPI]);
7981 RECORD_TIMER_START(m_timers[Timers::MPIComm]);
7982 MPI_Allreduce(&L2ErrLocal[0], &L2Error[0], noVars, MPI_DOUBLE, MPI_SUM, mpiComm(), AT_, "L2ErrLocal[0]",
7983 "L2Error[0]");
7984 MPI_Allreduce(&LInfErrLocal[0], &LInfError[0], noVars, MPI_DOUBLE, MPI_MAX, mpiComm(), AT_, "LInfErrLocal[0]",
7985 "LInfError[0]");
7986 RECORD_TIMER_STOP(m_timers[Timers::MPIComm]);
7987 RECORD_TIMER_STOP(m_timers[Timers::MPI]);
7988
7989 // Finalize L^2 error calculation
7990 for(MInt v = 0; v < noVars; v++) {
7991 L2ErrLocal[v] = sqrt(L2ErrLocal[v] / m_localVolume);
7992 L2Error[v] = sqrt(L2Error[v] / m_globalVolume);
7993 }
7994}
7995
7996
8001template <MInt nDim, class SysEqn>
8003 TRACE();
8004 RECORD_TIMER_START(m_timers[Timers::ResetRHS]);
8005
8006 resetBuffer(m_internalDataSize, &m_elements.rightHandSide(0));
8007
8008 RECORD_TIMER_STOP(m_timers[Timers::ResetRHS]);
8009}
8010
8011
8012template <MInt nDim, class SysEqn>
8014 TRACE();
8015 RECORD_TIMER_START(m_timers[Timers::ResetExternalSources]);
8016
8017 resetBuffer(m_internalDataSize, &m_elements.externalSource(0));
8018
8019 RECORD_TIMER_STOP(m_timers[Timers::ResetExternalSources]);
8020}
8021
8022
8032template <MInt nDim, class SysEqn>
8033void DgCartesianSolver<nDim, SysEqn>::resetBuffer(const MInt totalSize, MFloat* const buffer) {
8034 TRACE();
8035
8036#ifdef _OPENMP
8037#pragma omp parallel for
8038#endif
8039 for(MInt i = 0; i < totalSize; i++) {
8040 buffer[i] = 0.0;
8041 }
8042}
8043
8044
8060template <MInt nDim, class SysEqn>
8062 TRACE();
8063
8064 MInt foundElementId = -1;
8065 const MInt noElements = m_elements.size();
8066
8067 for(MInt elementId = 0; elementId < noElements; elementId++) {
8068 const MInt cellId = m_elements.cellId(elementId);
8069 const MFloat cellLength = grid().cellLengthAtCell(cellId);
8070 MBool pointInCell = true;
8071 MBool takeCell = true;
8072
8073 for(MInt i = 0; i < nDim; i++) {
8074 if(!(fabs(grid().tree().coordinate(cellId, i) - point[i]) <= cellLength * 0.5)) {
8075 pointInCell = false;
8076 }
8077 }
8078
8079 // Set globalUnique to true in order to avoid that points located on domain boundaries are
8080 // found multiple times (i.e. on different domains). This ensures that each point is globally
8081 // unique
8082 if(pointInCell && globalUnique) {
8083 for(MInt dimId = 0; dimId < nDim; dimId++) {
8084 const MFloat distance = grid().tree().coordinate(cellId, dimId) - point[dimId];
8085 // Check if the point is located on a cell edge
8086 if(approx(fabs(distance), cellLength * 0.5, MFloatEps)) {
8087 // Check the relative position of the point with respect to the cell center
8088 if(distance > 0.0) {
8089 // Point is on surface in negative coordinate direction, thus neighborDir is either
8090 // equal to 0, 2, or 4
8091 const MInt neighborDir = 2 * dimId;
8092 const MInt surfaceId = m_elements.surfaceIds(elementId, neighborDir);
8093 // Check for a MPI surface, if this is the case the unique point belongs to the
8094 // neighboring element on another domain
8095 if(isMpiSurface(surfaceId)) {
8096 takeCell = false;
8097 }
8098 }
8099 }
8100 }
8101 }
8102
8103 if(pointInCell && takeCell) {
8104 foundElementId = elementId;
8105 break;
8106 }
8107 }
8108
8109 return foundElementId;
8110}
8111
8112
8123// template <MInt nDim, class SysEqn>
8124// MInt DgCartesianSolver<nDim, SysEqn>::getCellIdAtPoint(const MFloat* point) {
8125// TRACE();
8126//
8127// const MInt elementId = getElementIdAtPoint(point);
8128//
8129// return (elementId == -1) ? -1 : m_elements.cellId(elementId);
8130//}
8131
8142template <MInt nDim, class SysEqn>
8144 TRACE();
8145 const MInt elementId = getElementIdAtPoint(point);
8146
8147 if(elementId == -1) {
8148 return false;
8149 }
8150
8151 calcStateAtPoint(point, elementId, state);
8152
8153 return true;
8154}
8155
8156
8170template <MInt nDim, class SysEqn>
8171void DgCartesianSolver<nDim, SysEqn>::calcStateAtPoint(const MFloat* const point, const MInt elementId,
8172 const MInt noVars, const MFloat* const u, MFloat* const state) {
8173 TRACE();
8174
8175 const MInt cellId = m_elements.cellId(elementId);
8176 const MInt polyDeg = m_elements.polyDeg(elementId);
8177 const MInt noNodes1D = m_elements.noNodes1D(elementId);
8178 const MInt noNodes1D3 = (nDim == 3) ? noNodes1D : 1;
8179 const DgInterpolation& interp = m_interpolation[polyDeg][noNodes1D];
8180
8181 const MFloat cellLength = grid().cellLengthAtCell(cellId);
8182
8183 // Vector saves the point with coordinates on the interval [17,1]
8184 MFloat pointOnUnitInt[nDim];
8185
8186 const MFloatTensor U(const_cast<MFloat*>(u), noNodes1D, noNodes1D, noNodes1D3, noVars);
8187
8188 // Because Lagrangefunctions are only defined on the interval [-1,1]
8189 // the coordinates are transformed to the interval.
8190 for(MInt n = 0; n < nDim; n++) {
8191 pointOnUnitInt[n] = F2 * (point[n] - grid().tree().coordinate(cellId, n)) / cellLength;
8192 }
8193
8194 if(m_sbpMode) {
8195 IF_CONSTEXPR(nDim == 2) {
8196 dg::interpolation::calcBilinearInterpolation(pointOnUnitInt, &interp.m_nodes[0], noNodes1D, noVars, u, state);
8197 }
8198 else {
8199 dg::interpolation::calcTrilinearInterpolation(pointOnUnitInt, &interp.m_nodes[0], noNodes1D, noVars, u, state);
8200 }
8201 } else {
8202 /*
8203 * Transform the coordinates of the Point to the intervall [-1,1]:
8204 * P(x,y,z) --> P(xi, eta, mu)
8205 *
8206 * To calculate the state Q at a Point P we use the interpolation formula:
8207 * Q(x,y,z) = Q(x(xi), y(eta), z(mu))
8208 * = \Sum_{i,j,k}^{N} q_{ijk} * l_i(xi) * l_j(eta) * l_k(mu)
8209 **/
8210
8211 MFloatVector lagrangePoly[nDim];
8212 for(MInt n = 0; n < nDim; n++) {
8213 lagrangePoly[n].resize(noNodes1D);
8214 fill_n(&lagrangePoly[n][0], noNodes1D, F0);
8215 }
8216
8217 for(MInt n = 0; n < nDim; n++) {
8218 dg::interpolation::calcLagrangeInterpolatingPolynomials(pointOnUnitInt[n], polyDeg, &interp.m_nodes[0],
8219 &interp.m_wBary[0], &lagrangePoly[n][0]);
8220 }
8221
8222 fill_n(state, noVars, 0.0);
8223
8224 IF_CONSTEXPR(nDim == 2) {
8225 for(MInt i = 0; i < noNodes1D; i++) {
8226 for(MInt j = 0; j < noNodes1D; j++) {
8227 for(MInt k = 0; k < noNodes1D3; k++) {
8228 for(MInt v = 0; v < noVars; v++) {
8229 state[v] += U(i, j, k, v) * lagrangePoly[0][i] * lagrangePoly[1][j];
8230 }
8231 }
8232 }
8233 }
8234 }
8235 else { // nDim == 3
8236 for(MInt i = 0; i < noNodes1D; i++) {
8237 for(MInt j = 0; j < noNodes1D; j++) {
8238 for(MInt k = 0; k < noNodes1D3; k++) {
8239 for(MInt v = 0; v < noVars; v++) {
8240 state[v] += U(i, j, k, v) * lagrangePoly[0][i] * lagrangePoly[1][j] * lagrangePoly[2][k];
8241 }
8242 }
8243 }
8244 }
8245 }
8246 }
8247}
8248
8250// template <MInt nDim, class SysEqn>
8251// MBool DgCartesianSolver<nDim, SysEqn>::calcNodeVarsAtPoint(const MFloat* const point,
8252// MFloat* const nodeVars) {
8253// TRACE();
8254// const MInt elementId = getElementIdAtPoint(point);
8255//
8256// if (elementId == -1) {
8257// return false;
8258// }
8259//
8260// calcStateAtPoint(point, elementId, SysEqn::noNodeVars(), &m_elements.nodeVars(elementId),
8261// nodeVars);
8262//
8263// return true;
8264//}
8265
8266
8268// template <MInt nDim, class SysEqn>
8269// void DgCartesianSolver<nDim, SysEqn>::calcNodeVarsAtPoint(const MFloat* const point,
8270// const MInt elementId,
8271// MFloat* const nodeVars) {
8272// calcStateAtPoint(point, elementId, SysEqn::noNodeVars(), &m_elements.nodeVars(elementId),
8273// nodeVars);
8274//}
8275
8276
8278template <MInt nDim, class SysEqn>
8280 const MInt elementId,
8281 MFloat* const state) {
8282 calcStateAtPoint(point, elementId, SysEqn::noVars(), &m_elements.variables(elementId), state);
8283}
8284
8285
8287template <MInt nDim, class SysEqn>
8289 const MInt sampleVarId, MFloat* const state,
8290 const MBool NotUsed(interpolate)) {
8291 switch(sampleVarId) {
8292 case DG_VARS: {
8293 // Evaluate solution state
8294 calcStateAtPoint(point, elementId, state);
8295 break;
8296 }
8297 case DG_NODEVARS: {
8298 // Evaluate node variables
8299 calcStateAtPoint(point, elementId, SysEqn::noNodeVars(), &m_elements.nodeVars(elementId), state);
8300 break;
8301 }
8302 case DG_SOURCETERMS: {
8303 // Evaluate external source terms
8304 calcStateAtPoint(point, elementId, SysEqn::noVars(), &m_elements.externalSource(elementId), state);
8305 break;
8306 }
8307 default: {
8308 TERMM(1, "sampling variable not supported");
8309 }
8310 }
8311}
8312
8313
8331template <MInt nDim, class SysEqn>
8333 TRACE();
8334 RECORD_TIMER_START(m_timers[Timers::InitMortarProjection]);
8335
8336 m_log << "Initializing mortar projections... ";
8337
8338 using namespace dg::interpolation;
8339
8340 // Init h-refinement mortar projection matrices
8341 // All four matrices for a particular polynomial degree are stored
8342 // coonsecutively. A second data structure with pointers stores the location
8343 // for each matrix.
8344
8345 m_projectionMatricesH.clear();
8346 m_projectionMatrixPointersH.clear();
8347 // Allocate storage for all h-refinement projection matrices
8348 MInt sizeH = 0;
8349 for(MInt noNodes1D = m_minNoNodes1D; noNodes1D <= m_maxNoNodes1D; noNodes1D++) {
8350 sizeH += 4 * noNodes1D * noNodes1D;
8351 }
8352 m_projectionMatricesH.resize(sizeH);
8353
8354 // Store pointers to matrices
8355 m_projectionMatrixPointersH.resize(4 * (m_maxNoNodes1D + 1));
8356 MInt offset = 0;
8357 for(MInt noNodes1D = m_minNoNodes1D; noNodes1D <= m_maxNoNodes1D; noNodes1D++) {
8358 for(MInt p = 0; p < 4; p++) {
8359 m_projectionMatrixPointersH[4 * noNodes1D + p] = &m_projectionMatricesH[offset];
8360 offset += noNodes1D * noNodes1D;
8361 }
8362 }
8363
8364 // Calculate matrices
8365 if(m_sbpMode) {
8366 for(MInt noNodes1D = m_minNoNodes1D; noNodes1D <= m_maxNoNodes1D; noNodes1D++) {
8367 using namespace sbp::mortar;
8368 // Calculate fwd,bwd x lwr,upr projections matrices (all at onces)
8369 calcMortarProjectionMatrixHSBP(m_initPolyDeg, m_sbpOperator, mortarH<dg::mortar::forward>(noNodes1D, lower),
8370 mortarH<dg::mortar::forward>(noNodes1D, upper),
8371 mortarH<dg::mortar::reverse>(noNodes1D, lower),
8372 mortarH<dg::mortar::reverse>(noNodes1D, upper));
8373 }
8374 } else {
8375 for(MInt polyDeg = m_minPolyDeg; polyDeg <= m_maxPolyDeg; polyDeg++) {
8376 const MInt noNodes1D = polyDeg + 1;
8377 const DgInterpolation& interp = m_interpolation[polyDeg][noNodes1D];
8378 const MFloat* const nodes = &interp.m_nodes[0];
8379 const MFloat* const wBary = &interp.m_wBary[0];
8380 using namespace dg::mortar;
8381
8382 // Calculate first forward projection matrix
8383 calcMortarProjectionMatrixHForward(polyDeg, nodes, wBary, dg::mortar::lower,
8384 mortarH<dg::mortar::forward>(noNodes1D, dg::mortar::lower));
8385
8386 // Calculate second forward projection matrix
8387 calcMortarProjectionMatrixHForward(polyDeg, nodes, wBary, dg::mortar::upper,
8388 mortarH<dg::mortar::forward>(noNodes1D, dg::mortar::upper));
8389
8390 // Calculate first reverse projection matrix
8391 calcMortarProjectionMatrixHReverse(polyDeg, nodes, wBary, dg::mortar::lower,
8392 mortarH<dg::mortar::reverse>(noNodes1D, dg::mortar::lower));
8393
8394 // Calculate second reverse projection matrix
8395 calcMortarProjectionMatrixHReverse(polyDeg, nodes, wBary, dg::mortar::upper,
8396 mortarH<dg::mortar::reverse>(noNodes1D, dg::mortar::upper));
8397 }
8398 }
8399
8400 // Init p-refinement mortar projection matrices
8401 // The forward/reverse matrices for a particular polyDegHi/polyDegLo
8402 // combination are stored consecutively. A second data structure stores
8403 // pointers to the beginning of each matrix.
8404
8405 m_projectionMatricesP.clear();
8406 m_projectionMatrixPointersP.clear();
8407 // Allocate storage for all p-refinement projection matrices
8408 MInt sizeP = 0;
8409 for(MInt polyDegHi = m_minPolyDeg; polyDegHi <= m_maxPolyDeg; polyDegHi++) {
8410 for(MInt polyDegLo = m_minPolyDeg; polyDegLo < polyDegHi; polyDegLo++) {
8411 sizeP += 2 * (polyDegHi + 1) * (polyDegLo + 1);
8412 }
8413 }
8414 m_projectionMatricesP.resize(sizeP);
8415
8416 // Store pointers to matrices
8417 m_projectionMatrixPointersP.resize(m_maxPolyDeg * (m_maxPolyDeg + 1));
8418 sizeP = 0;
8419 for(MInt polyDegHi = m_minPolyDeg; polyDegHi <= m_maxPolyDeg; polyDegHi++) {
8420 for(MInt polyDegLo = m_minPolyDeg; polyDegLo < polyDegHi; polyDegLo++) {
8421 for(MInt p = 0; p < 2; p++) {
8422 const MInt idx = 2 * (polyDegHi * (polyDegHi - 1) / 2 + polyDegLo) + p;
8423 m_projectionMatrixPointersP[idx] = &m_projectionMatricesP[sizeP];
8424 sizeP += (polyDegHi + 1) * (polyDegLo + 1);
8425 }
8426 }
8427 }
8428
8429 // Calculate matrices
8430 if(m_sbpMode) {
8431 // Read SBP Projection Matrices
8432 for(MInt polyDegHi = m_minPolyDeg; polyDegHi <= m_maxPolyDeg; polyDegHi++) {
8433 for(MInt polyDegLo = m_minPolyDeg; polyDegLo < polyDegHi; polyDegLo++) {
8434 auto operatorLo = m_sbpOperator;
8435 auto operatorHi = m_sbpOperator;
8436
8437 for(MUint j = 0; j < m_prefPatchesPolyDeg.size(); j++) {
8438 if(polyDegLo == (MInt)m_prefPatchesPolyDeg[j]) {
8439 operatorLo = m_prefPatchesOperators[j];
8440 }
8441 if(polyDegHi == (MInt)m_prefPatchesPolyDeg[j]) {
8442 operatorHi = m_prefPatchesOperators[j];
8443 }
8444 }
8445
8446 sbp::mortar::calcMortarProjectionMatrixPSBP(operatorLo, operatorHi, polyDegLo, polyDegHi,
8447 mortarP<dg::mortar::forward>(polyDegLo, polyDegHi),
8448 mortarP<dg::mortar::reverse>(polyDegLo, polyDegHi));
8449 }
8450 }
8451
8452 } else {
8453 // Calculate DG Projection Matrices by using Lagrange interpolation
8454 for(MInt polyDegHi = m_minPolyDeg; polyDegHi <= m_maxPolyDeg; polyDegHi++) {
8455 const MInt noNodes1DHi = polyDegHi + 1;
8456 const DgInterpolation& interpHi = m_interpolation[polyDegHi][noNodes1DHi];
8457 for(MInt polyDegLo = m_minPolyDeg; polyDegLo < polyDegHi; polyDegLo++) {
8458 const MInt noNodes1DLo = polyDegLo + 1;
8459 const DgInterpolation& interpLo = m_interpolation[polyDegLo][noNodes1DLo];
8460
8461 const MFloat* const nodesHi = &interpHi.m_nodes[0];
8462 const MFloat* const wBaryHi = &interpHi.m_wBary[0];
8463 const MFloat* const nodesLo = &interpLo.m_nodes[0];
8464 const MFloat* const wBaryLo = &interpLo.m_wBary[0];
8465
8466 // Calculate forward projection matrix from lower to higher polynomial
8467 // degree
8468 dg::mortar::calcMortarProjectionMatrixP(polyDegLo, nodesLo, wBaryLo, polyDegHi, nodesHi,
8469 mortarP<dg::mortar::forward>(polyDegLo, polyDegHi));
8470
8471 // Calculate reverse projection matrix from higher to lower polynomial
8472 // degree
8473 dg::mortar::calcMortarProjectionMatrixP(polyDegHi, nodesHi, wBaryHi, polyDegLo, nodesLo,
8474 mortarP<dg::mortar::reverse>(polyDegLo, polyDegHi));
8475 }
8476 }
8477 }
8478 m_log << "done" << endl;
8479
8480 RECORD_TIMER_STOP(m_timers[Timers::InitMortarProjection]);
8481}
8482
8483
8496template <MInt nDim, class SysEqn>
8497template <MBool forward, MInt noVars>
8499 const MInt dir,
8500 MFloat* source,
8501 MFloat* destination,
8502 ElementCollector& elem,
8503 SurfaceCollector& surf) {
8504 calcMortarProjectionH<forward, noVars>(srfcId, dir, source, destination, elem, surf);
8505 calcMortarProjectionP<forward, noVars>(srfcId, dir, source, destination, elem, surf);
8506}
8507
8508
8525template <MInt nDim, class SysEqn>
8526template <MBool forward, MInt noVars>
8528 const MInt dir,
8529 MFloat* source,
8530 MFloat* destination,
8531 ElementCollector& elem,
8532 SurfaceCollector& surf) {
8533 // TRACE();
8534
8535 // Skip if this is not a p-refined surface
8536 const MInt side = 1 - dir % 2;
8537 const MInt elementId = surf.nghbrElementIds(srfcId, side);
8538 const MInt elementPolyDeg = elem.polyDeg(elementId);
8539 const MInt surfacePolyDeg = surf.polyDeg(srfcId);
8540 if(elementPolyDeg == surfacePolyDeg) {
8541 return;
8542 }
8543
8544 // Calculate auxiliary variables for better readability
8545 const MInt surfaceNodes1D = surf.noNodes1D(srfcId);
8546 const MInt surfaceNodes1D3 = (nDim == 3) ? surfaceNodes1D : 1;
8547 const MInt elementNodes1D = elem.noNodes1D(elementId);
8548 const MInt elementNodes1D3 = (nDim == 3) ? elementNodes1D : 1;
8549
8550 MFloatTensor src;
8551 MFloatTensor dest;
8552
8553 if(forward) {
8554 src = MFloatTensor(source, elementNodes1D, elementNodes1D3, noVars);
8555 dest = MFloatTensor(destination, surfaceNodes1D, surfaceNodes1D3, noVars);
8556 } else {
8557 src = MFloatTensor(source, surfaceNodes1D, surfaceNodes1D3, noVars);
8558 dest = MFloatTensor(destination, surfaceNodes1D, surfaceNodes1D3, noVars);
8559 }
8560
8561 // Reset destination matrix to 0
8562 dest.set(0.0);
8563
8564 // Determine matrix sizes
8565 const MInt size0 = forward ? surfaceNodes1D : elementNodes1D;
8566 const MInt size1 = forward ? elementNodes1D : surfaceNodes1D;
8567
8568 // Select projection matrix
8569 MFloatMatrix p(mortarP<forward>(elementPolyDeg, surfacePolyDeg), size0, size1);
8570
8571
8572 // Apply projection
8573 IF_CONSTEXPR(nDim == 2) {
8574 // 2D version
8575 for(MInt i = 0; i < size0; i++) {
8576 for(MInt j = 0; j < size1; j++) {
8577 for(MInt var = 0; var < noVars; var++) {
8578 dest(i, 0, var) += src(j, 0, var) * p(i, j);
8579 }
8580 }
8581 }
8582 }
8583 else {
8584 // 3D version
8585 for(MInt i = 0; i < size0; i++) {
8586 for(MInt s = 0; s < size0; s++) {
8587 for(MInt j = 0; j < size1; j++) {
8588 for(MInt k = 0; k < size1; k++) {
8589 for(MInt var = 0; var < noVars; var++) {
8590 dest(i, s, var) += src(j, k, var) * p(i, j) * p(s, k);
8591 }
8592 }
8593 }
8594 }
8595 }
8596 }
8597}
8598
8599
8616template <MInt nDim, class SysEqn>
8617template <MBool forward, MInt noVars>
8619 const MInt dir,
8620 MFloat* source,
8621 MFloat* destination,
8622 ElementCollector& elem,
8623 SurfaceCollector& surf) {
8624 // TRACE(); //causes a huge performance hit
8625
8626 // Skip if this is not an h-refined surface
8627 const MInt side = 1 - dir % 2;
8628 const MInt elementId = surf.nghbrElementIds(srfcId, side);
8629 if(surf.fineCellId(srfcId) == -1 || surf.fineCellId(srfcId) == elem.cellId(elementId)) {
8630 return;
8631 }
8632
8633 // Determine polynomial degree(s) and number of nodes
8634 const MInt elementPolyDeg = elem.polyDeg(elementId);
8635 const MInt surfacePolyDeg = surf.polyDeg(srfcId);
8636 const MInt elementNoNodes1D = elem.noNodes1D(elementId);
8637 const MInt surfaceNoNodes1D = surf.noNodes1D(srfcId);
8638 const MInt noNodes1D = forward ? elementNoNodes1D : surfaceNoNodes1D;
8639 const MInt noNodes1D3 = (nDim == 3) ? noNodes1D : 1;
8640
8641 // Select source/destination storage
8642 MFloatTensor src(source, noNodes1D, noNodes1D3, noVars);
8643 MFloatTensor dest(destination, noNodes1D, noNodes1D3, noVars);
8644
8645 // Reset destination matrix to 0
8646 dest.set(0.0);
8647
8648 // Select correct projection matrix for h-refinement
8649 const MInt cellIdR = elem.cellId(elementId);
8650 const MInt cellIdL = surf.fineCellId(srfcId);
8651 const MInt orientation = surf.orientation(srfcId);
8652
8653 // Select projection matrix
8654 const MInt dirA = (orientation == 0) ? 1 : 0;
8655 const MInt positionA = (grid().tree().coordinate(cellIdR, dirA) < grid().tree().coordinate(cellIdL, dirA))
8658
8659 const MFloatMatrix pA(mortarH<forward>(noNodes1D, positionA), noNodes1D, noNodes1D);
8660
8661 // Apply projection
8662 IF_CONSTEXPR(nDim == 2) {
8663 // 2D version
8664 for(MInt i = 0; i < noNodes1D; i++) {
8665 for(MInt j = 0; j < noNodes1D; j++) {
8666 for(MInt var = 0; var < noVars; var++) {
8667 dest(i, 0, var) += src(j, 0, var) * pA(i, j);
8668 }
8669 }
8670 }
8671 }
8672 else {
8673 // 3D version
8674 // Select additional projection matrix
8675 const MInt dirB = (orientation == 2) ? 1 : 2;
8676 const MInt positionB = (grid().tree().coordinate(cellIdL, dirB) < grid().tree().coordinate(cellIdR, dirB))
8679
8680 const MFloatMatrix pB(mortarH<forward>(noNodes1D, positionB), noNodes1D, noNodes1D);
8681
8682 for(MInt i = 0; i < noNodes1D; i++) {
8683 for(MInt j = 0; j < noNodes1D; j++) {
8684 for(MInt k = 0; k < noNodes1D; k++) {
8685 for(MInt l = 0; l < noNodes1D; l++) {
8686 for(MInt var = 0; var < noVars; var++) {
8687 dest(i, j, var) += src(k, l, var) * pA(i, k) * pB(j, l);
8688 }
8689 }
8690 }
8691 }
8692 }
8693 }
8694
8695 // Copy results to source if p-projection is necessary
8696 // TODO labels:DG Move this p-refinement code out of a h-refinement method
8697 if(elementPolyDeg != surfacePolyDeg) {
8698 const MInt size = src.dim0() * src.dim1() * src.dim2();
8699 copy_n(&dest[0], size, &src[0]);
8700 }
8701}
8702
8703
8718template <MInt nDim, class SysEqn>
8719template <MBool forward>
8720MFloat* DgCartesianSolver<nDim, SysEqn>::mortarP(const MInt sourcePolyDeg, const MInt targetPolyDeg) {
8721 // Sanity check
8722 ASSERT(sourcePolyDeg < targetPolyDeg,
8723 "source polynomial degree (= " + to_string(sourcePolyDeg)
8724 + ") must be lower than target polynomial degree (= " + to_string(targetPolyDeg) + ")");
8725
8726 // Calculate matrix index from polyomial degrees
8727 const MInt index = 2 * (targetPolyDeg * (targetPolyDeg - 1) / 2 + sourcePolyDeg) + (1 - forward);
8728
8729 // Return pointer to first element in projection matrix
8730 return m_projectionMatrixPointersP[index];
8731}
8732
8733
8747template <MInt nDim, class SysEqn>
8748template <MBool forward>
8750 // Sanity check
8751 ASSERT(position == 0 || position == 1, "position must be `0` or `1` (=" + to_string(position) + ")");
8752
8753 // Calculate matrix index relative from polynomial degree and position
8754 const MInt index = 4 * noNodes1D + 2 * (1 - forward) + position;
8755
8756 // Return pointer to first element in projection matrix
8757 return m_projectionMatrixPointersH[index];
8758}
8759
8760
8766template <MInt nDim, class SysEqn>
8768 TRACE();
8769
8770 m_log << "Exchanging polynomial degrees of MPI surfaces... ";
8771
8772 // Create communication buffers
8773 vector<vector<MInt>> sendBuffer(m_noExchangeNghbrDomains);
8774 vector<vector<MInt>> recvBuffer(m_noExchangeNghbrDomains);
8775 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
8776 sendBuffer[i].resize(m_mpiSurfaces[i].size());
8777 recvBuffer[i].resize(m_mpiSurfaces[i].size());
8778 }
8779
8780 // Exchange data with each neighbor exchange domain
8781 vector<MPI_Request> recvRequests(m_noExchangeNghbrDomains, MPI_REQUEST_NULL);
8782 vector<MPI_Request> sendRequests(m_noExchangeNghbrDomains, MPI_REQUEST_NULL);
8783 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
8784 // Copy data from surfaces to send buffer
8785 for(vector<MInt>::size_type j = 0; j < m_mpiSurfaces[i].size(); j++) {
8786 sendBuffer[i][j] = m_surfaces.polyDeg(m_mpiSurfaces[i][j]);
8787 }
8788
8789 // Begin MPI exchange
8790 MPI_Irecv(&recvBuffer[i][0], m_mpiSurfaces[i].size(), maia::type_traits<MInt>::mpiType(), m_exchangeNghbrDomains[i],
8791 m_exchangeNghbrDomains[i], mpiComm(), &recvRequests[i], AT_, "recvBuffer[i][0]");
8792 MPI_Isend(&sendBuffer[i][0], m_mpiSurfaces[i].size(), maia::type_traits<MInt>::mpiType(), m_exchangeNghbrDomains[i],
8793 domainId(), mpiComm(), &sendRequests[i], AT_, "sendBuffer[i][0]");
8794 }
8795
8796 // Wait for MPI exchange to complete
8797 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
8798 MPI_Wait(&sendRequests[i], MPI_STATUS_IGNORE, AT_);
8799 MPI_Wait(&recvRequests[i], MPI_STATUS_IGNORE, AT_);
8800 }
8801
8802 // Copy data from receive buffer to surfaces
8803 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
8804 for(vector<MInt>::size_type j = 0; j < m_mpiSurfaces[i].size(); j++) {
8805 const MInt srfcId = m_mpiSurfaces[i][j];
8806 const MInt currentPolyDeg = m_surfaces.polyDeg(srfcId);
8807 const MInt receivedPolyDeg = recvBuffer[i][j];
8808
8809 // If new polynomial degree is lower than current polynomial degree,
8810 // change surface polynomial degree and recalculate node coordinates
8811 if(currentPolyDeg < receivedPolyDeg) {
8812 m_surfaces.polyDeg(srfcId) = receivedPolyDeg;
8813 m_surfaces.noNodes1D(srfcId) = receivedPolyDeg + 1;
8814 // Recompute surface node coordinates
8815 calcSurfaceNodeCoordinates(srfcId);
8816 }
8817 }
8818 }
8819
8820 m_log << "done" << endl;
8821}
8822
8823
8837template <MInt nDim, class SysEqn>
8839 TRACE();
8840 RECORD_TIMER_START(m_timers[Timers::AdaptiveRefinement]);
8841
8842 // Empty set to hold surface ids of refined elements
8843 set<MInt> adaptedSrfcs;
8844
8845 // Vector to hold error estimate
8846 const MInt noElements = m_elements.size();
8847 vector<MFloat> errorEstimate(noElements, 0.0);
8848
8849 // Update the error estimate of the elements
8850 calcErrorEstimate(errorEstimate);
8851
8852 // Determine maximum error on local domain
8853 MFloat localErrorEstimate = *max_element(errorEstimate.begin(), errorEstimate.end());
8854 MFloat buffer = -1.0;
8855
8856 // exchange local max. error estimate to obtain global max. error
8857 if(noDomains() > 1) {
8858 MPI_Allreduce(&localErrorEstimate, &buffer, 1, maia::type_traits<MFloat>::mpiType(), MPI_MAX, mpiComm(), AT_,
8859 "localErrorEstimate", "buffer");
8860 }
8861
8862 const MFloat maxErrorEstimate = (buffer > localErrorEstimate) ? buffer : localErrorEstimate;
8863
8864#ifdef _OPENMP
8865#pragma omp parallel for
8866#endif
8867 // Determine which elments need to be refined
8868 for(MInt elementId = 0; elementId < noElements; elementId++) {
8869 const MInt cellId = m_elements.cellId(elementId);
8870 const MFloat error = errorEstimate[elementId];
8871
8872 // Initialize adapted polynomial degree to current value
8873 MInt adaptedPolyDeg = m_elements.polyDeg(elementId);
8874
8879 if(m_adaptivePref == DG_ADAPTIVE_TEST) {
8880 if(grid().tree().coordinate(cellId, 0) > 0) {
8881 adaptedPolyDeg += 1;
8882 }
8883 }
8884
8904 else if(m_adaptivePref == DG_ADAPTIVE_GRADIENT) {
8905 // Determine relative error threshold
8906 MFloat lvlThreshold = maxErrorEstimate * m_adaptiveThreshold;
8907
8908 // Iterate over all possible polynomial degrees (e.g. between minPolyDeg
8909 // and maxPolyDeg)
8910 for(MInt refLvl = m_maxPolyDeg; refLvl > m_minPolyDeg; refLvl--) {
8911 // If error is below current threshold, set new polynomialDegree
8912 if(error > lvlThreshold || refLvl == m_minPolyDeg) {
8913 adaptedPolyDeg = refLvl;
8914 break;
8915 }
8916 // Decrease threshold
8917 lvlThreshold *= m_adaptiveThreshold;
8918 }
8919 } else {
8920 TERMM(1, "Invalid adaptive refinement case.");
8921 }
8922
8923 // Skip elements for which no refinement is necessary
8924 if(adaptedPolyDeg == m_elements.polyDeg(elementId)) {
8925 continue;
8926 }
8927
8928 // Do not allow elements to be refined over m_maxPolyDeg
8929 if(adaptedPolyDeg > m_maxPolyDeg) {
8930 m_log << "WARNING: Element polynomial degree would exceed maximum "
8931 "polynomial degree."
8932 << endl;
8933 cout << "WARNING: Element polynomial degree would exceed maximum "
8934 "polynomial degree."
8935 << endl;
8936 adaptedPolyDeg = m_maxPolyDeg;
8937 }
8938
8939 // Do not allow elements to be coarsed lower than m_minPolyDeg
8940 if(adaptedPolyDeg < m_minPolyDeg) {
8941 m_log << "WARNING: Element polynomial degree would be coarsed below "
8942 "minimum polynomial degree."
8943 << endl;
8944 cout << "WARNING: Element polynomial degree would be coarsed below "
8945 "minimum polynomial degree."
8946 << endl;
8947 adaptedPolyDeg = m_minPolyDeg;
8948 }
8949
8950 // Interpolate element to degree specified by adaptedPolyDeg
8951 interpolateElement(elementId, adaptedPolyDeg);
8952 }
8953
8954 const MInt begin = 0;
8955 const MInt end = m_surfaces.size();
8956
8957 // Adapt all surfaces to the new polynomial degree
8958 MInt noAdaptedSrfcs = 0;
8959#ifdef _OPENMP
8960#pragma omp parallel for reduction(+ : noAdaptedSrfcs)
8961#endif
8962 for(MInt srfcId = begin; srfcId < end; srfcId++) {
8963 const MInt srfcPolyDeg = m_surfaces.polyDeg(srfcId);
8964 const MInt internalSide = m_surfaces.internalSideId(srfcId);
8965 const MInt elementIdL = m_surfaces.nghbrElementIds(srfcId, (internalSide == -1) ? 0 : internalSide);
8966 const MInt elementIdR = m_surfaces.nghbrElementIds(srfcId, (internalSide == -1) ? 1 : internalSide);
8967 const MInt polyDegL = m_elements.polyDeg(elementIdL);
8968 const MInt polyDegR = m_elements.polyDeg(elementIdR);
8969 const MInt noNodes1DL = m_elements.noNodes1D(elementIdL);
8970 const MInt noNodes1DR = m_elements.noNodes1D(elementIdR);
8971
8972 // If the maximum polynomial degree of the adjacent elements has changed,
8973 // update polynomial degree and re-calculate surface node coordinates
8974 if(max(polyDegL, polyDegR) != srfcPolyDeg) {
8975 m_surfaces.polyDeg(srfcId) = max(polyDegL, polyDegR);
8976 m_surfaces.noNodes1D(srfcId) = max(noNodes1DL, noNodes1DR);
8977 calcSurfaceNodeCoordinates(srfcId);
8978 noAdaptedSrfcs++;
8979 }
8980 }
8981
8982 // Update MPI surface polynomial degree
8983 if(hasMpiExchange()) {
8984 exchangeMpiSurfacePolyDeg();
8985 }
8986
8987 // Recalculate values that depend on element polynomial degree
8988 m_noTotalNodesXD = 0;
8989 for(MInt i = 0; i < m_elements.size(); i++) {
8990 const MInt noNodesXD = m_elements.noNodesXD(i);
8991 m_noTotalNodesXD += noNodesXD;
8992 }
8993
8994 cout << "Grid has been refined at timestep " << timeStep << " (adapted surfaces: " << noAdaptedSrfcs << " )" << endl;
8995 m_log << "Grid has been refined at timestep " << timeStep << " (adapted surfaces: " << noAdaptedSrfcs << " )" << endl;
8996
8997 RECORD_TIMER_STOP(m_timers[Timers::AdaptiveRefinement]);
8998}
8999
9000
9008template <MInt nDim, class SysEqn>
9009void DgCartesianSolver<nDim, SysEqn>::calcErrorEstimate(vector<MFloat>& errorEstimate) {
9010 TRACE();
9011
9012 if(m_adaptivePref == DG_ADAPTIVE_GRADIENT) {
9013 // Calculate the gradient over all surfaces to determine the maximum
9014 // gradient for each element
9015 const MInt begin = m_innerSurfacesOffset;
9016 const MInt end = m_surfaces.size();
9017 const MInt noVars = SysEqn::noVars();
9018 const MInt noElements = m_elements.size();
9019
9020 MFloatScratchSpace error(m_surfaces.size(), FUN_, "Error estimate");
9021 fill_n(&error[0], m_surfaces.size(), 0.0);
9022
9023// Loop over all surfaces, calculate error estimates, and update elements
9024#ifdef _OPENMP
9025#pragma omp parallel for
9026#endif
9027 for(MInt srfcId = begin; srfcId < end; srfcId++) {
9028 const MInt noNodes1D = m_surfaces.noNodes1D(srfcId);
9029 const MInt noNodes1D3 = (nDim == 3) ? noNodes1D : 1;
9030 const MFloat* stateL = &m_surfaces.variables(srfcId, 0);
9031 const MFloat* stateR = &m_surfaces.variables(srfcId, 1);
9032
9033
9034 const MFloatTensor uL(const_cast<MFloat*>(stateL), noNodes1D, noNodes1D3, noVars);
9035 const MFloatTensor uR(const_cast<MFloat*>(stateR), noNodes1D, noNodes1D3, noVars);
9036
9037 // Calculate error estimate as difference between left and right state
9038 for(MInt i = 0; i < noNodes1D; i++) {
9039 for(MInt j = 0; j < noNodes1D3; j++) {
9040 for(MInt var = 0; var < noVars; var++) {
9041 error[srfcId] += fabs(uL(i, j, var) - uR(i, j, var));
9042 }
9043 }
9044 }
9045
9046 // Normalize by number of nodes on surface
9047 error[srfcId] = error[srfcId] / (noNodes1D * noNodes1D3);
9048 }
9049
9050#ifdef _OPENMP
9051#pragma omp parallel for
9052#endif
9053 // sum element error contributions
9054 for(MInt elementId = 0; elementId < noElements; elementId++) {
9055 const MInt noSrfcs = 2 * nDim;
9056 for(MInt i = 0; i < noSrfcs; i++) {
9057 const MInt srfcId = m_elements.surfaceIds(elementId, i);
9058 errorEstimate[elementId] += error[srfcId];
9059 }
9060 }
9061 }
9062}
9063
9064
9074template <MInt nDim, class SysEqn>
9075void DgCartesianSolver<nDim, SysEqn>::interpolateElement(const MInt elementId, const MInt adaptedPolyDeg) {
9076 TRACE();
9077
9078 // Interpolate element data to new integration nodes at new polynomial degree
9079 // in temporary storage, then copy result to destination location
9080 // (do not use Scratch space to remain thread-safe!)
9081 const MInt adaptedNodes1D = adaptedPolyDeg + 1;
9082 const MInt adaptedNodes1D3 = (nDim == 3) ? adaptedNodes1D : 1;
9083 const MInt noVars = SysEqn::noVars();
9084 const MInt elemPolyDeg = m_elements.polyDeg(elementId);
9085 const MInt elemNodes1D = m_elements.noNodes1D(elementId);
9086 const MInt size = adaptedNodes1D * adaptedNodes1D * adaptedNodes1D3 * noVars;
9087 vector<MFloat> buffer(size);
9088 using namespace dg::interpolation;
9089
9090 // Variables
9091 if(elemPolyDeg < adaptedPolyDeg) {
9092 interpolateNodes<nDim>(&m_elements.variables(elementId), mortarP<dg::mortar::forward>(elemPolyDeg, adaptedPolyDeg),
9093 elemNodes1D, adaptedNodes1D, noVars, &buffer[0]);
9094 copy(buffer.begin(), buffer.end(), &m_elements.variables(elementId));
9095
9096 // Time Integration Storage
9097 interpolateNodes<nDim>(&m_elements.timeIntStorage(elementId),
9098 mortarP<dg::mortar::forward>(elemPolyDeg, adaptedPolyDeg), elemNodes1D, adaptedNodes1D,
9099 noVars, &buffer[0]);
9100 copy(buffer.begin(), buffer.end(), &m_elements.timeIntStorage(elementId));
9101 } else {
9102 interpolateNodes<nDim>(&m_elements.variables(elementId), mortarP<dg::mortar::reverse>(adaptedPolyDeg, elemPolyDeg),
9103 elemNodes1D, adaptedNodes1D, noVars, &buffer[0]);
9104 copy(buffer.begin(), buffer.end(), &m_elements.variables(elementId));
9105
9106 // Time Integration Storage
9107 interpolateNodes<nDim>(&m_elements.timeIntStorage(elementId),
9108 mortarP<dg::mortar::reverse>(adaptedPolyDeg, elemPolyDeg), elemNodes1D, adaptedNodes1D,
9109 noVars, &buffer[0]);
9110 copy(buffer.begin(), buffer.end(), &m_elements.timeIntStorage(elementId));
9111 }
9112
9113 // Update polynomial degree
9114 m_elements.polyDeg(elementId) = adaptedPolyDeg;
9115 m_elements.noNodes1D(elementId) = adaptedNodes1D;
9116
9117 // Recalculate node coordinates of the element
9118 calcElementNodeCoordinates(elementId);
9119}
9120
9121
9135template <MInt nDim, class SysEqn>
9137 // TRACE();
9138
9139 const MInt cellId = m_elements.cellId(elementId);
9140 MInt nghbrCellId = grid().tree().neighbor(cellId, dir);
9141
9142 // If neigbor cell does not exist on current level, check parent level
9143 if(nghbrCellId < 0 && grid().tree().hasParent(cellId)) {
9144 nghbrCellId = grid().tree().neighbor(grid().tree().parent(cellId), dir);
9145 }
9146
9147 return nghbrCellId >= 0;
9148}
9149
9150
9158template <MInt nDim, class SysEqn>
9160 // TRACE();
9161 ASSERT(elementId >= 0, "Invalid elementId");
9162
9163 const MInt cellId = m_elements.cellId(elementId);
9164 const MInt level = grid().tree().level(cellId);
9165
9166 return level;
9167}
9168
9169
9179// template <MInt nDim, class SysEqn>
9180// MInt DgCartesianSolver<nDim, SysEqn>::getLevelOfNeighborElement(const MInt elementId,
9181// const MInt dir) {
9182// // TRACE();
9183// ASSERT(elementId >= 0, "Invalid elementId");
9184//
9185// const MInt cellId = m_elements.cellId(elementId);
9186//
9187// return getLevelOfDirectNeighborCell(cellId, dir);
9188//}
9189
9190
9204template <MInt nDim, class SysEqn>
9206 // TRACE();
9207
9208 const MInt nghbrCellId = grid().tree().neighbor(cellId, dir);
9209 const MInt cellLvl = grid().tree().level(cellId);
9210
9211 // Check for finer nghbr. The finer nghbr must be directly adjacent to current cell.
9212 if(nghbrCellId > 0 && grid().tree().hasChildren(nghbrCellId)) {
9213 TERMM(1, "The following is not yet tested! If it works, delete this TERMM!");
9214 for(MInt child = 0; child < IPOW2(nDim); child++) {
9215 if(!(childCodePro[dir] & (1 << child))) continue;
9216 if(grid().tree().child(nghbrCellId, child) > -1) return cellLvl + 1;
9217 }
9218 }
9219
9220 // If neigbor cell does not exist on current level, check parent level
9221 if(nghbrCellId < 0 && grid().tree().hasParent(cellId)) {
9222 if(grid().tree().neighbor(grid().tree().parent(cellId), dir)) {
9223 return cellLvl - 1;
9224 }
9225 }
9226
9227 return nghbrCellId < 0 ? -1 : cellLvl;
9228}
9229
9230
9234template <MInt nDim, class SysEqn>
9236 TRACE();
9237
9238 return (m_adaptivePref > 0 && hasPref());
9239}
9240
9244template <MInt nDim, class SysEqn>
9246 TRACE();
9247
9248 return (m_pref == 1 && m_minPolyDeg != m_maxPolyDeg);
9249}
9250
9251
9258template <MInt nDim, class SysEqn>
9260 TRACE();
9261
9262 // Check if adaptive p-refinement is enabled: The adaptation is done once
9263 // after the first time step (i.e. before the second time step), and then
9264 // before each adaptation interval
9265 return (hasAdaptivePref() && (timeStep == 2 || timeStep % m_adaptiveInterval == 0));
9266}
9267
9268
9275template <MInt nDim, class SysEqn>
9277 TRACE();
9278
9279 const MInt begin = m_mpiSurfacesOffset;
9280 const MInt end = m_mpiSurfacesOffset + m_noMpiSurfaces;
9281 MBool isMpiSrfc = false;
9282
9283 if(begin <= id && id < end) {
9284 isMpiSrfc = true;
9285 }
9286
9287 return isMpiSrfc;
9288}
9289
9290
9297template <MInt nDim, class SysEqn>
9299 TRACE();
9300
9301 MInt hElementId = -1;
9302
9303 const MInt noHElements = m_helements.size();
9304 for(MInt hId = 0; hId < noHElements; hId++) {
9305 if(m_helements.elementId(hId) == elementId) {
9306 hElementId = hId;
9307 break;
9308 }
9309 }
9310
9311 return hElementId;
9312}
9313
9314
9318template <MInt nDim, class SysEqn>
9320 TRACE();
9321
9322 finalizeMpiExchange();
9323
9324 // Set to -1 such that the new size is calculated in allocateAndInitSolverMemory
9325 m_maxNoSurfaces = -1;
9326
9327 // Cleanup communication memory
9328 for(MInt i = 0; i < m_noExchangeNghbrDomains; i++) {
9329 m_mpiSurfaces[i].clear();
9330 m_sendBuffers[i].clear();
9331 m_recvBuffers[i].clear();
9332 }
9333 m_mpiSurfaces.clear();
9334 m_sendBuffers.clear();
9335 m_recvBuffers.clear();
9336 m_exchangeNghbrDomains.clear();
9337 m_sendRequests.clear();
9338 m_recvRequests.clear();
9339 m_noExchangeNghbrDomains = 0;
9340
9341 m_isInitSolver = false;
9342}
9343
9344
9348template <MInt nDim, class SysEqn>
9350 TRACE();
9351
9352 MInt totalNoDgLT = 0;
9353 // Add a load type for each polynomial degree (even if there might be no element with this
9354 // polyomial degree)
9355 totalNoDgLT += m_maxPolyDeg - m_minPolyDeg + 1;
9356
9357 // Add load type for boundary condition elements (assumes only one polynomial degree)
9358 if(m_weightDgRbcElements) {
9359 totalNoDgLT += 1;
9360 }
9361
9362 return totalNoDgLT;
9363}
9364
9365
9367template <MInt nDim, class SysEqn>
9368void DgCartesianSolver<nDim, SysEqn>::getDefaultWeights(MFloat* weights, std::vector<MString>& names) const {
9369 TRACE();
9370
9371 const MInt noPolyDegs = m_maxPolyDeg - m_minPolyDeg + 1;
9372 for(MInt i = 0; i < noPolyDegs; i++) {
9373 weights[i] = 0.2;
9374 names[i] = "dg_node_p" + std::to_string(m_minPolyDeg + i);
9375 }
9376
9377 if(m_weightDgRbcElements) {
9378 weights[noPolyDegs] = 0.5;
9379 names[noPolyDegs] = "dg_node_rbc";
9380 }
9381}
9382
9383
9389template <MInt nDim, class SysEqn>
9391 TRACE();
9392
9393 // Reset
9394 std::fill_n(&loadQuantities[0], noLoadTypes(), 0);
9395
9396 // Nothing to do if solver is not active
9397 if(!isActive()) {
9398 return;
9399 }
9400
9401 const MInt noPolyDegs = m_maxPolyDeg - m_minPolyDeg + 1;
9402
9403 if(noPolyDegs > 1) {
9404 // Total DOFs of different polynomial degrees
9405 for(MInt i = 0; i < noPolyDegs; i++) {
9406 loadQuantities[i] = m_statLocalNoActiveDOFsPolyDeg[i];
9407 }
9408 } else {
9409 // Only one polynomial degree
9410 loadQuantities[0] = m_statLocalNoActiveDOFs;
9411 }
9412
9413 // Add number of boundary condition element nodes
9414 if(m_weightDgRbcElements) {
9415 loadQuantities[noPolyDegs] = 0;
9416 for(auto&& bc : m_boundaryConditions) {
9417 loadQuantities[noPolyDegs] += bc->getLocalNoNodes();
9418 }
9419 }
9420}
9421
9422
9430template <MInt nDim, class SysEqn>
9431MFloat DgCartesianSolver<nDim, SysEqn>::getCellLoad(const MInt gridCellId, const MFloat* const weights) const {
9432 TRACE();
9433 ASSERT(isActive(), "solver is not active");
9434
9435 // Convert to solver cell id and check
9436 const MInt cellId = grid().tree().grid2solver(gridCellId);
9437 if(cellId < 0) {
9438 return 0;
9439 }
9440
9441 if(cellId < 0 || cellId >= grid().noInternalCells()) {
9442 TERMM(1, "The given cell id is invalid.");
9443 }
9444
9445 const MInt elementId = m_elements.getElementByCellId(cellId);
9446
9447 // Default cell load
9448 MFloat cellLoad = 0.0;
9449
9450 // Add load if there is an element
9451 if(elementId != -1) {
9452 const MInt polyDeg = m_elements.polyDeg(elementId);
9453 const MInt noNodesXD = m_elements.noNodesXD(elementId);
9454 // Weight the number of nodes and add a constant weight for each element
9455 // Note: m_weightPerElement should only be != 0 when getCellLoad is called from setCellWeights!
9456 cellLoad = m_weightPerElement + noNodesXD * weights[polyDeg - m_minPolyDeg];
9457
9458 // Add weight for boundary condition element
9459 if(m_weightDgRbcElements) {
9460 for(auto&& bc : m_boundaryConditions) {
9461 if(bc->hasBcElement(elementId)) {
9462 cellLoad += noNodesXD * weights[m_maxPolyDeg - m_minPolyDeg + 1];
9463 }
9464 }
9465 }
9466 }
9467
9468 return cellLoad;
9469}
9470
9471
9473template <MInt nDim, class SysEqn>
9475 TRACE();
9476 ASSERT(isActive(), "solver is not active");
9477
9478 const MInt noCells = grid().noInternalCells();
9479 const MInt noCellsGrid = grid().raw().treeb().size();
9480 const MInt noWeights = noLoadTypes();
9481 const MInt offset = solverId() * noCellsGrid;
9482
9483 std::vector<MFloat> weights(noWeights);
9484 std::fill(weights.begin(), weights.end(), m_weightPerNode);
9485
9486 for(MInt cellId = 0; cellId < noCells; cellId++) {
9487 const MInt gridCellId = grid().tree().solver2grid(cellId);
9488 solverCellWeight[offset + gridCellId] = getCellLoad(gridCellId, &weights[0]);
9489 }
9490}
9491
9492
9496template <MInt nDim, class SysEqn>
9498 TRACE();
9499
9500 // Nothing to do if solver is not active
9501 if(!isActive()) {
9502 return;
9503 }
9504
9505 const MInt domainOffset = grid().domainOffset(domainId());
9506 const MInt noElements = m_elements.size();
9507 // Store the global cell id in all elements
9508 for(MInt elementId = 0; elementId < noElements; elementId++) {
9509 const MInt globalCellId = m_elements.cellId(elementId) + domainOffset;
9510 m_elements.cellId(elementId) = globalCellId;
9511 }
9512}
9513
9514
9518template <MInt nDim, class SysEqn>
9520 TRACE();
9521
9522 // Nothing to do if solver is not active
9523 if(!isActive()) {
9524 return;
9525 }
9526
9527 const MInt domainOffset = grid().domainOffset(domainId());
9528 const MInt noElements = m_elements.size();
9529 // Change global cell ids to local cell ids
9530 for(MInt elementId = 0; elementId < noElements; elementId++) {
9531 const MInt localCellId = m_elements.cellId(elementId) - domainOffset;
9532 m_elements.cellId(elementId) = localCellId;
9533 }
9534}
9535
9536
9543template <MInt nDim, class SysEqn>
9545 TRACE();
9546
9547 if(!isActive()) {
9548 TERMM(1, "Error: cellDataTypeDlb() might give wrong results on inactive ranks.");
9549 return -1;
9550 }
9551
9552 MInt dataType = -1;
9553 if(dataId > -1 && dataId < noDgCartesianSolverCellData()) {
9554 // DG solver cell data
9555 dataType = s_cellDataTypeDlb[dataId];
9556 } else if(dataId >= noDgCartesianSolverCellData() && dataId < noCellDataDlb()) {
9557 // Boundary condition cell data
9558 MInt offset = noDgCartesianSolverCellData();
9559 for(auto&& bc : m_boundaryConditions) {
9560 const MInt bcNoCellData = bc->noCellDataDlb();
9561 if(dataId >= offset && dataId < offset + bcNoCellData) {
9562 dataType = bc->cellDataTypeDlb(dataId - offset);
9563 break;
9564 }
9565 offset += bcNoCellData;
9566 }
9567 } else {
9568 TERMM(1, "The requested dataId is not valid: " + to_string(dataId) + " (" + to_string(noDgCartesianSolverCellData())
9569 + ", " + to_string(noCellDataDlb()) + ")");
9570 }
9571
9572 return dataType;
9573}
9574
9575
9583template <MInt nDim, class SysEqn>
9585 TRACE();
9586
9587 // Inactive ranks do not have any data to communicate
9588 if(!isActive()) {
9589 return 0;
9590 }
9591
9592 // Convert to solver cell id and check
9593 const MInt cellId = grid().tree().grid2solver(gridCellId);
9594 if(cellId < 0) {
9595 return 0;
9596 }
9597
9598 MInt dataSize = 0;
9599
9600 if(dataId > -1 && dataId < noDgCartesianSolverCellData()) {
9601 // DG solver cell data
9602 const MInt elementId = m_elements.getElementByCellId(cellId);
9603
9604 if(elementId != -1) {
9605 const MInt noNodesXD = m_elements.noNodesXD(elementId);
9606 switch(dataId) {
9607 case CellData::ELEM_CELL_ID:
9608 case CellData::ELEM_POLY_DEG:
9609 dataSize = 1;
9610 break;
9611 case CellData::ELEM_NO_NODES_1D:
9612 dataSize = 1;
9613 break;
9614 case CellData::ELEM_VARIABLES:
9615 dataSize = noNodesXD * SysEqn::noVars();
9616 break;
9617 case CellData::ELEM_NODE_VARS:
9618 dataSize = noNodesXD * SysEqn::noNodeVars();
9619 break;
9620 default:
9621 TERMM(1, "Unknown data id. (" + to_string(dataId) + ")");
9622 break;
9623 }
9624 }
9625 } else if(dataId >= noDgCartesianSolverCellData() && dataId < noCellDataDlb()) {
9626 // Boundary condition cell data
9627 MInt offset = noDgCartesianSolverCellData();
9628 for(auto&& bc : m_boundaryConditions) {
9629 const MInt bcNoCellData = bc->noCellDataDlb();
9630 if(dataId >= offset && dataId < offset + bcNoCellData) {
9631 dataSize = bc->cellDataSizeDlb(dataId - offset, cellId);
9632 break;
9633 }
9634 offset += bcNoCellData;
9635 }
9636 } else {
9637 TERMM(1, "The requested dataId is not valid.");
9638 }
9639
9640 return dataSize;
9641}
9642
9643
9647template <MInt nDim, class SysEqn>
9649 TRACE();
9650
9651 // Set reinitialization stage
9652 m_loadBalancingReinitStage = 0;
9653
9654 // Store currently used memory
9655 const MLong previouslyAllocated = allocatedBytes();
9656
9657 // Set to prevent initialization (e.g. due to coupling) that overwrites redistributed data
9658 m_restart = true;
9659
9660 // Update the grid proxy for this solver
9661 grid().update();
9662
9663 // Just reset parallelization information if solver is not active and cleanup containers
9664 if(!isActive()) {
9665 updateDomainInfo(-1, -1, MPI_COMM_NULL, AT_);
9666 m_elements.reset(0);
9667 m_helements.reset(0);
9668 m_surfaces.reset(0);
9669 return;
9670 }
9671
9672 // Set new domain info for solver
9673 updateDomainInfo(grid().domainId(), grid().noDomains(), grid().mpiComm(), AT_);
9674
9675 allocateAndInitSolverMemory();
9676
9677 // Print information on used memory
9678 printAllocatedMemory(previouslyAllocated, "DgCartesianSolver (solverId = " + to_string(m_solverId) + ")", mpiComm());
9679
9680 RECORD_TIMER_START(m_timers[Timers::RunInit]);
9681 RECORD_TIMER_START(m_timers[Timers::InitSolverObjects]);
9682 // Initialization from initSolver()
9683 initGridMap();
9684 initElements();
9685 initHElements();
9686 // Note: Polynomial degree is communicated and set from gridcontroller
9687 RECORD_TIMER_STOP(m_timers[Timers::InitSolverObjects]);
9688 RECORD_TIMER_STOP(m_timers[Timers::RunInit]);
9689}
9690
9691
9695template <MInt nDim, class SysEqn>
9697 TRACE();
9698
9699 // Nothing to do if solver is not active
9700 if(!isActive()) {
9701 return;
9702 }
9703
9704 // Set reinitialization stage
9705 m_loadBalancingReinitStage = 1;
9706
9707 RECORD_TIMER_START(m_timers[Timers::RunInit]);
9708 RECORD_TIMER_START(m_timers[Timers::InitSolverObjects]);
9709
9710 initInterpolation();
9711
9712 // Set new internal data size
9713 m_internalDataSize = m_elements.size() * pow(m_maxNoNodes1D, nDim) * SysEqn::noVars();
9714
9715 // Calculate new total number of nodes
9716 m_noTotalNodesXD = 0;
9717 for(MInt i = 0; i < m_elements.size(); i++) {
9718 const MInt noNodesXD = m_elements.noNodesXD(i);
9719 m_noTotalNodesXD += noNodesXD;
9720 }
9721
9722 initNodeCoordinates();
9723
9724 initJacobian();
9725
9726 checkCellProperties();
9727
9728 // Prevent the RBC from overwriting its solution
9729 m_restart = true;
9730
9731 initSurfaces();
9732
9733 if(noDomains() > 1) {
9734 initMpiExchange();
9735 }
9736
9737 if(useSponge()) {
9738 m_log << "Reinitializing sponge... ";
9739 sponge().init(m_maxPolyDeg, &grid(), &m_elements, &m_surfaces, &m_boundaryConditions, &m_sysEqn, mpiComm());
9740 m_log << "done" << endl;
9741 }
9742
9743 if(noDomains() > 1 && hasPref()) {
9744 exchangeMpiSurfacePolyDeg();
9745 }
9746
9747 initMortarProjections();
9748
9749 // @ansgar TODO labels:DG,PP wont work, no cleanup, save data prior to DLB? also the point ordering in the
9750 // output file might change
9751 m_pointData.init();
9752 m_surfaceData.init();
9753 m_volumeData.init();
9754
9755 m_slice.init();
9756
9757 if(m_pointData.enabled() || m_surfaceData.enabled()) {
9758 TERMM(1, "DLB for point/surface/volumeData not supported yet");
9759 }
9760
9761 initSimulationStatistics();
9762
9763 // From initData()
9764 // Reset current Runge Kutta stage
9765 m_rkStage = 0;
9766
9767 // Update all node variables, if they are used
9768 if(SysEqn::noNodeVars() > 0) {
9769 updateNodeVariables();
9770 extendNodeVariables(); // @ansgar TODO labels:DG,totest,toremove check if needed!
9771 }
9772
9773 m_isInitSolver = true;
9774 m_isInitData = true;
9775 // TODO labels:DG endAutoSaveTime
9776 m_isInitMainLoop = true;
9777
9778 // outputInitSummary();
9779 RECORD_TIMER_STOP(m_timers[Timers::InitSolverObjects]);
9780 RECORD_TIMER_STOP(m_timers[Timers::RunInit]);
9781
9782 // Set reinitialization stage
9783 m_loadBalancingReinitStage = 2;
9784}
9785
9786
9788template <MInt nDim, class SysEqn>
9789void DgCartesianSolver<nDim, SysEqn>::getGlobalSolverVars(std::vector<MFloat>& globalFloatVars,
9790 std::vector<MInt>& globalIntVars) {
9791 TRACE();
9792
9793 globalFloatVars.push_back(m_time);
9794 globalFloatVars.push_back(m_dt);
9795
9796 globalIntVars.push_back(m_timeStep);
9797 globalIntVars.push_back(m_firstTimeStep);
9798 globalIntVars.push_back(m_noAnalyzeTimeSteps);
9799}
9800
9801
9803template <MInt nDim, class SysEqn>
9804void DgCartesianSolver<nDim, SysEqn>::setGlobalSolverVars(std::vector<MFloat>& globalFloatVars,
9805 std::vector<MInt>& globalIntVars) {
9806 TRACE();
9807
9808 m_time = globalFloatVars[0];
9809 m_dt = globalFloatVars[1];
9810
9811 m_timeStep = globalIntVars[0];
9812 m_firstTimeStep = globalIntVars[1];
9813 m_noAnalyzeTimeSteps = globalIntVars[2];
9814}
9815
9816
9818template <MInt nDim, class SysEqn>
9819void DgCartesianSolver<nDim, SysEqn>::getSolverTimings(std::vector<std::pair<MString, MFloat>>& solverTimings,
9820 const MBool allTimings) {
9821 TRACE();
9822 const MString namePrefix = "b" + std::to_string(solverId()) + "_";
9823
9824 const MFloat load = returnLoadRecord();
9825 const MFloat idle = returnIdleRecord();
9826
9827 solverTimings.emplace_back(namePrefix + "loadDgCartesianSolver", load);
9828 solverTimings.emplace_back(namePrefix + "idleDgCartesianSolver", idle);
9829
9830#ifdef MAIA_TIMER_FUNCTION
9831 solverTimings.emplace_back(namePrefix + "timeStepRk", RETURN_TIMER_TIME(m_timers[Timers::RungeKuttaStep]));
9832
9833 if(allTimings) {
9834 // Full set of timings
9835 solverTimings.emplace_back(namePrefix + "calcDgTimeDerivative", RETURN_TIMER_TIME(m_timers[Timers::TimeDeriv]));
9836
9837 solverTimings.emplace_back(namePrefix + "resetRHS", RETURN_TIMER_TIME(m_timers[Timers::ResetRHS]));
9838 solverTimings.emplace_back(namePrefix + "prolong_to_surfaces", RETURN_TIMER_TIME(m_timers[Timers::Prolong]));
9839 solverTimings.emplace_back(namePrefix + "forward_projection",
9840 RETURN_TIMER_TIME(m_timers[Timers::ForwardProjection]));
9841
9842 solverTimings.emplace_back(namePrefix + "MPI_surface_exchange", RETURN_TIMER_TIME(m_timers[Timers::SurfExchange]));
9843 solverTimings.emplace_back(namePrefix + "communication", RETURN_TIMER_TIME(m_timers[Timers::SurfExchangeComm]));
9844 solverTimings.emplace_back(namePrefix + "copy_operations", RETURN_TIMER_TIME(m_timers[Timers::SurfExchangeCopy]));
9845 solverTimings.emplace_back(namePrefix + "waiting", RETURN_TIMER_TIME(m_timers[Timers::SurfExchangeWait]));
9846 solverTimings.emplace_back(namePrefix + "waiting_send", RETURN_TIMER_TIME(m_timers[Timers::SEWaitSend]));
9847 solverTimings.emplace_back(namePrefix + "waiting_recv", RETURN_TIMER_TIME(m_timers[Timers::SEWaitRecv]));
9848
9849 solverTimings.emplace_back(namePrefix + "calcVolumeIntegral", RETURN_TIMER_TIME(m_timers[Timers::VolInt]));
9850
9851 solverTimings.emplace_back(namePrefix + "flux_calculation", RETURN_TIMER_TIME(m_timers[Timers::Flux]));
9852 solverTimings.emplace_back(namePrefix + "calcBoundarySurfaceFlux", RETURN_TIMER_TIME(m_timers[Timers::FluxBndry]));
9853 solverTimings.emplace_back(namePrefix + "calcInnerSurfaceFlux", RETURN_TIMER_TIME(m_timers[Timers::FluxInner]));
9854 solverTimings.emplace_back(namePrefix + "calcMpiSurfaceFlux", RETURN_TIMER_TIME(m_timers[Timers::FluxMPI]));
9855
9856 solverTimings.emplace_back(namePrefix + "surface_integrals", RETURN_TIMER_TIME(m_timers[Timers::SurfInt]));
9857 solverTimings.emplace_back(namePrefix + "applyJacobian", RETURN_TIMER_TIME(m_timers[Timers::Jacobian]));
9858 solverTimings.emplace_back(namePrefix + "calcSourceTerms", RETURN_TIMER_TIME(m_timers[Timers::Sources]));
9859 solverTimings.emplace_back(namePrefix + "resetExtSources",
9860 RETURN_TIMER_TIME(m_timers[Timers::ResetExternalSources]));
9861 solverTimings.emplace_back(namePrefix + "applyExternalSources",
9862 RETURN_TIMER_TIME(m_timers[Timers::ExternalSources]));
9863 solverTimings.emplace_back(namePrefix + "calcSpongeTerms", RETURN_TIMER_TIME(m_timers[Timers::Sponge]));
9864
9865 solverTimings.emplace_back(namePrefix + "time_integration", RETURN_TIMER_TIME(m_timers[Timers::TimeInt]));
9866
9867 solverTimings.emplace_back(namePrefix + "IO", RETURN_TIMER_TIME(m_timers[Timers::MainLoopIO]));
9868 solverTimings.emplace_back(namePrefix + "solution_analysis", RETURN_TIMER_TIME(m_timers[Timers::Analysis]));
9869 solverTimings.emplace_back(namePrefix + "adaptive_refinement",
9870 RETURN_TIMER_TIME(m_timers[Timers::AdaptiveRefinement]));
9871 } else {
9872 // Reduced/essential set of timings
9873 // MPI exchange
9874 solverTimings.emplace_back(namePrefix + "MPI_surface_exchange", RETURN_TIMER_TIME(m_timers[Timers::SurfExchange]));
9875 // Boundary conditions
9876 solverTimings.emplace_back(namePrefix + "calcBoundarySurfaceFlux", RETURN_TIMER_TIME(m_timers[Timers::FluxBndry]));
9877 }
9878#endif
9879}
9880
9881
9883template <MInt nDim, class SysEqn>
9885 std::vector<std::pair<MString, MInt>>& domainInfo) {
9886 TRACE();
9887
9888 const MString namePrefix = "b" + std::to_string(solverId()) + "_";
9889
9890 // Number of DG elements
9891 const MInt noElements = m_elements.size();
9892 domainInfo.emplace_back(namePrefix + "noDgElements", noElements);
9893
9894 // Number of additional boundary condition elements
9895 MInt noBcElements = 0;
9896 for(auto&& bc : m_boundaryConditions) {
9897 for(MInt elementId = 0; elementId < noElements; elementId++) {
9898 if(bc->hasBcElement(elementId)) {
9899 noBcElements++;
9900 }
9901 }
9902 }
9903 domainInfo.emplace_back(namePrefix + "noDgBcElements", noBcElements);
9904}
9905
9906
9908template <MInt nDim, class SysEqn>
9910 std::vector<MInt>& noSamplingVars,
9911 std::vector<std::vector<MString>>& samplingVarNames,
9912 const MString featureName) {
9913 TRACE();
9914
9915 // Read sampling variable names (for a specific feature)
9916 std::vector<MString> varNamesList;
9917 MInt noSampleVars = readSolverSamplingVarNames(varNamesList, featureName);
9918
9919 // Set default sampling variables if none specified
9920 if(noSampleVars == 0) {
9921 varNamesList.push_back("DG_VARS");
9922 noSampleVars = 1;
9923 }
9924
9925 for(MInt i = 0; i < noSampleVars; i++) {
9926 const MInt samplingVar = string2enum(varNamesList[i]);
9927 std::vector<MString> varNames;
9928
9929 auto samplingVarIt = std::find(samplingVarIds.begin(), samplingVarIds.end(), samplingVar);
9930 if(samplingVarIt != samplingVarIds.end()) {
9931 TERMM(1, "Sampling variable '" + varNamesList[i] + "' already specified.");
9932 }
9933
9934 switch(samplingVar) {
9935 case DG_VARS: {
9936 const MInt noVars = SysEqn::noVars();
9937
9938 samplingVarIds.push_back(DG_VARS);
9939 noSamplingVars.push_back(noVars);
9940
9941 varNames.resize(noVars);
9942 for(MInt varId = 0; varId < noVars; varId++) {
9943 // TODO labels:DG needs to be fixed if conservative and primitive variables are different
9944 varNames[varId] = SysEqn::consVarNames(varId);
9945 }
9946
9947 samplingVarNames.push_back(varNames);
9948 break;
9949 }
9950 case DG_NODEVARS: {
9951 const MInt noVars = SysEqn::noNodeVars();
9952
9953 samplingVarIds.push_back(DG_NODEVARS);
9954 noSamplingVars.push_back(noVars);
9955
9956 varNames.resize(noVars);
9957 for(MInt varId = 0; varId < noVars; varId++) {
9958 varNames[varId] = SysEqn::nodeVarNames(varId);
9959 }
9960 samplingVarNames.push_back(varNames);
9961 break;
9962 }
9963 case DG_SOURCETERMS: {
9964 const MInt noVars = SysEqn::noVars();
9965
9966 samplingVarIds.push_back(DG_SOURCETERMS);
9967 noSamplingVars.push_back(noVars);
9968
9969 varNames.resize(noVars);
9970 for(MInt varId = 0; varId < noVars; varId++) {
9971 varNames[varId] = "source_" + SysEqn::consVarNames(varId);
9972 }
9973 samplingVarNames.push_back(varNames);
9974 break;
9975 }
9976 default: {
9977 TERMM(1, "Unknown sampling variable: " + varNamesList[i]);
9978 break;
9979 }
9980 }
9981 }
9982}
9983
9984
9985// Enable/disable compiler-specific diagnostics
9986#if defined(MAIA_GCC_COMPILER)
9987#pragma GCC diagnostic pop
9988#endif
MLong allocatedBytes()
Return the number of allocated bytes.
Definition: alloc.cpp:121
static MInt propertyLength(const MString &name, MInt solverId=m_noSolvers)
Returns the number of elements of a property.
Definition: context.cpp:538
static MBool propertyExists(const MString &name, MInt solver=m_noSolvers)
This function checks if a property exists in general.
Definition: context.cpp:494
MBool needHElementForCell(const MInt cellId)
Return true if h-element is needed for cell, false otherwise.
std::array< MInt, 2 *nDim > getBoundaryConditionIds(const MInt cellId)
Determine if element is boundary is cut by geometry elements and return corresponding boundary condit...
MInt noLoadTypes() const override
Return the number of DG load types.
void resetBuffer(const MInt totalSize, MFloat *const buffer)
Reset the given buffer to zero.
DgBoundaryConditionFactory< nDim, SysEqn > m_boundaryConditionFactory
MBool hasMpiExchange() const
void prolongToSurfaces()
Extrapolate the solution from inside the elements to the surfaces.
MFloat * mortarP(const MInt sourcePolyDeg, const MInt targetPolyDeg)
void checkCellProperties()
Check all relevant bit properties in the cells.
void calcSurfaceIntegral()
Calculate the surface integral for all faces of element and update dU/dt.
void applyJacobian()
Adds the negative of the inverse Jacobian to the time derivative.
void preTimeStep() override
Perform pre-time-step operations, e.g. set new dt if required.
void initTimers()
Initialize all solver-wide timers and start the solver timer.
void calcInnerSurfaceFlux()
Calculate the numerical flux on the internal surfaces and update m_flux.
MBool calcStateAtPoint(const MFloat *point, MFloat *state)
returns the cellId of a cell containing a given point
typename DgBoundaryConditionFactory< nDim, SysEqn >::ReturnType BC
void calcMortarProjectionH(const MInt srfcId, const MInt dir, MFloat *source, MFloat *destination, ElementCollector &elem, SurfaceCollector &surf)
Calculate the h-refinement mortar projection.
MInt getLevelOfDirectNeighborCell(const MInt cellId, const MInt dir)
void applyExternalSourceTerms(const MFloat time)
Add the external coupling source terms to the right hand side.
MInt getHElementId(const MInt elementId) const
Return h-element id of a given element (if it exists).
void createHElement(const MInt cellId)
Create h-element for cell with id cellId.
MFloat calcTimeStep()
Calculate the next time step.
void loadNodeVarsFile()
Load node variables from file.
void calcSamplingVarAtPoint(const MFloat *point, const MInt elementId, const MInt sampleVarId, MFloat *state, const MBool interpolate) override
Calculate the state vector at a given point and for the specified sampling variable.
void createElement(const MInt cellId)
Create element for cell with id cellId.
void initSurfaces()
Create for all elements and directions surfaces if necessary.
void setGlobalSolverVars(std::vector< MFloat > &NotUsed(globalFloatVars), std::vector< MInt > &NotUsed(globalIdVars)) override
Set global solver variables for DLB (same on each domain)
MBool pointIsInside(const MFloat *const coordinates)
Check if point is inside the computational domain.
void calcMpiSurfaceFlux()
Calculate the numerical flux on the MPI surfaces and update m_flux.
void initJacobian()
Calculates the inverse Jacobian for each element.
MBool hasAdaptivePref() const
Return true if adaptive hp-refinement is activated.
void getSolverSamplingProperties(std::vector< MInt > &samplingVarIds, std::vector< MInt > &noSamplingVars, std::vector< std::vector< MString > > &samplingVarNames, const MString featureName="") override
Return sampling properties for the DG solver.
void getLoadQuantities(MInt *const loadQuantities) const override
Return the cumulative load quantities on this domain.
void writeRestartFile(const MBool writeRestart, const MBool writeBackup, const MString gridFileName, MInt *recalcIdTree) override
Write a restart file.
MInt initMpiSurface(const MInt elementId, const MInt dir)
Check if the surface of the element in the given direction is a MPI surface. If it is,...
MInt calculateNeededNoSurfaces()
Determine the number of surfaces that need to be created on this domain.
MBool solutionStep() override
Perform one Runge-Kutta step/stage.
MInt initInnerSurface(const MInt elementId, const MInt dir)
Check if the surface of the element in the given direction is an inner surface without h/p-refinement...
void cleanUp() override
Clean up after a simulation run.
MString getRestartFileName(const MInt timeStep, const MInt useNonSpecifiedRestartFile)
Return name of a restart file for the given time step.
virtual void saveRestartFile()
Saves a file to disk with all information that is necessary to restart the calculations from here.
MBool isMpiSurface(const MInt id) const
Return true if a surface is a MPI surface.
MBool hasSurface(const MInt elementId, const MInt dir)
Check if a surface exists for the element in the given direction.
void calcMortarProjection(const MInt srfcId, const MInt dir, MFloat *source, MFloat *destination, ElementCollector &elem, SurfaceCollector &surf)
void initDgCartesianSolver()
Initializes basic properties of the solver.
void globalToLocalIds() override
Change global into local ids.
void initMortarProjections()
Calculate all necessary mortar projection matrices.
MInt createHMPISurfaces(const MInt elementId, const MInt dir)
MInt cellDataTypeDlb(const MInt dataId) const override
Get data type of cell data.
void checkGridMap(const MString &donorGridFileName)
Perform some sanity checks on loaded grid map.
void setCellWeights(MFloat *solverCellWeight) override
Set cell weights with constant weighting factor weightPerNode.
std::array< MInt, Timers::_count > m_timers
void calcSourceTerms(MFloat t)
Calculates the source terms for each node and adds them to the time derivative of the conservative va...
void saveNodeVarsFile()
Save node variables to file.
void extendNodeVariables()
Extends nodeVars from given planes to given directions.
void updateNodeVariablesSingleElement(const MInt elementId, const MInt extendDir, MIntTensor hForwardSurfaceId, MIntTensor hReverseSurfaceId, std::vector< MBool > &cellHasUpdatedMeans, std::vector< MInt > &SurfaceWantsToMPISend, MBool &noMoreUpdates)
Sets nodeVars of an element to values on the surface opposite to extendDir.
void adaptiveRefinement(const MInt timeStep)
Apply adaptive refinement (right now: only p-refinement)
void initNodeCoordinates()
Calculates the coordinates of the integration nodes within each element.
~DgCartesianSolver() override
Frees resources that were allocated in the constructor and that are not automatically released when t...
void calcVolumeIntegral()
Calculate the volume integral for all elements and update m_rightHandSide.
void calcElementNodeCoordinates(const MInt elementId)
Calculates the coordinates of the integration nodes within the element.
DgCartesianSolver(const MInt solverId, GridProxy &gridProxy_, Geometry< nDim > &geometry_, const MPI_Comm comm)
Constructor of the DG solver reads properties and allocates solver resources (as far as they are know...
MFloat * mortarH(const MInt polyDeg, const MInt position)
void initPolynomialDegree()
Calculate and set initial polynomial degree and number of nodes in all elements.
void loadGridMap(const MString &gridMapFileName)
Load previously created grid map.
void initInterpolation()
Calculates necessary coefficients for interpolation and stores them once for the whole solver.
void initElements()
Initialize all elements by iterating over all cells and creating an element for each internal leaf ce...
void calcMortarProjectionP(const MInt srfcId, const MInt dir, MFloat *source, MFloat *destination, ElementCollector &elem, SurfaceCollector &surf)
Calculate the p-refinement mortar projection.
void calcBoundarySurfaceFlux(MFloat t)
Calculate the numerical flux on the boundary surfaces and update m_flux.
void saveSolverSolution(const MBool forceOutput, const MBool finalTimeStep) override
Save the solver solution, i.e. write solution files and sampling/slice output.
void calcErrorEstimate(std::vector< MFloat > &errorEstimate)
Calculate error estimate for adaptive hp-refinement.
void initSolver() override
Initialize the solver.
void allocateAndInitSolverMemory()
Allocates main memory resources.
void analyzeTimeStep(MFloat t, MFloat runTimeRelative, MFloat runTimeTotal, MInt timeStep, MFloat dt)
Calculates and prints the L^2 and L^inf errors (using calcErrorNorms()) for the current time.
void initData()
Initialize solver data, i.e. set initial conditions or load restart file.
MFloat getCellLoad(const MInt cellId, const MFloat *const weights) const override
Return the load of a single cell (given computational weights).
MInt getLevelByElementId(const MInt elementId)
MBool needElementForCell(const MInt cellId)
Return true if element is needed for cell, false otherwise.
MBool hasPref() const
Return true if p-refinement is set.
MBool hasNeighborCell(const MInt elementId, const MInt dir)
void getSolverTimings(std::vector< std::pair< MString, MFloat > > &solverTimings, const MBool allTimings) override
Get solver timings.
MInt cellDataSizeDlb(const MInt dataId, const MInt cellId) override
Return data size of cell data.
void interpolateElement(const MInt elementId, const MInt newPolyDeg)
Interpolate an element to a different polynomial degree.
void initMainLoop()
Perform all operations that prepare the execution of the main loop.
void getGlobalSolverVars(std::vector< MFloat > &NotUsed(globalFloatVars), std::vector< MInt > &NotUsed(globalIntVars)) override
Get global solver variables that need to be communicated for DLB (same on each domain)
void getDomainDecompositionInformation(std::vector< std::pair< MString, MInt > > &domainInfo) override
Return decomposition information, i.e. number of local elements,...
void balancePost() override
Reinitialize solver after setting solution data.
void calcErrorNorms(const MFloat t, std::vector< MFloat > &L2Error, std::vector< MFloat > &LInfError, std::vector< MFloat > &L2ErrLocal, std::vector< MFloat > &LInfErrLocal)
Calculate the L^2 and L^infinity error norms at a given time. The error is calculated in comparison t...
typename CartesianSolver::GridProxy GridProxy
void applySurfaceIntegral(MFloat *rhs, const MInt polyDeg, const MInt noNodes1D, const MInt srfcId, const MInt side, const MFloat *flux, SurfaceCollector &surf)
Calculate the surface integral for a face of an element and update dU/dt.
MInt initBoundarySurface(const MInt elementId, const MInt dir)
Check if the surface of the element in the given direction is a boundary surface. If it is init,...
MInt createSurface(const MInt elementId, const MInt dir, MInt nghbrId=-1)
void cancelMpiRequests() override
Cancel open MPI (receive) requests.
void timeStepRk(const MFloat t, const MFloat dt, const MInt substep=-1)
Time integration using the five-stage fourth-order low-storage Runge-Kutta scheme as described in the...
void saveNodalData(const MString &fileNameBase, const MInt noVars, const std::vector< MString > &varNames, const MFloat *const data) const
Save nodal data to file.
void calcDgTimeDerivative(const MFloat t, const MFloat tStage)
Main routine to calculate the discontinuous Galerkin time derivative. After this method was called,...
void subTimeStepRk(const MFloat dt, const MInt stage, const MInt totalSize, const MFloat *const rhs, MFloat *const variables, MFloat *const timeIntStorage)
Perform one Runge-Kutta substep on the given elements.
void extendNodeVariablesSingleDirection(const MInt extendDir, const MFloat extendOffset, const MFloat extendLimit)
Set nodeVars upstream of given plane to values of elements in plane.
MInt getElementIdAtPoint(const MFloat *point, MBool globalUnique=false)
returns the elementId of a element containing a given point
void initPrefinement()
Set polynomial degree for static p-refinement case.
void initGridMap()
Determine grid-to-grid mapping.
void run()
Main method to run this solver.
MBool prepareRestart(MBool, MBool &) override
Return true if the solver wants to write a restart file.
void finalizeInitSolver() override
Finalization of the solver initialization.
void localToGlobalIds() override
Change local into global ids.
void finishMpiSurfaceExchange()
Finish sending the window-side data and finish receiving the halo-side data for all MPI surfaces.
void initHElements()
Initialize all helements by iterating over all cells and creating an element for each coarse cell in ...
typename maia::CartesianSolver< nDim, DgCartesianSolver > CartesianSolver
void postTimeStep()
Perform post-time-step operations, e.g. advance time, error analysis.
MBool step(const MFloat externalDt=-std::numeric_limits< MFloat >::infinity(), const MBool substep=false)
Advance the solution by one time step.
MBool isAdaptationTimeStep(const MInt timeStep) const
Return if the given timestep is an adaptation timestep.
void resetSolver() override
Reset solver (for load balancing)
void setNumericalProperties()
Reads properties associated with the numerical method.
void calcSurfaceNodeCoordinates(const MInt surfaceId)
void loadRestartFile() override
Load restart file with all information that is necessary to restart the calculations from here.
void balancePre() override
Reinitialize solver prior to setting solution data in DLB.
void initSolverObjects()
Initializes the solver. This must be called before using any of the discretization methods,...
void startMpiSurfaceExchange()
Start sending the window-side data and start receiving the halo-side data for all MPI surfaces.
void updateNodeVariables()
Update all node variables at the surfaces.
MBool isMpiRoot() const
Return true if this is the root rank of the solver MPI communicator.
virtual void initialCondition()
Set the initial condition in all elements.
void saveSolutionFile()
Saves all available data to disk.
void getDefaultWeights(MFloat *weights, std::vector< MString > &names) const
Return the default weights for all load quantities.
void outputInitSummary()
Print initialization summary to user and m_log.
virtual void resetRHS()
Reset the time derivative of the conservative variables to zero.
Class stores precalculated values for interpolation & integration on the reference interval [-1,...
MFloatVector m_LhatFace[2]
void calcNormal(const MFloat *const vertices, MFloat *normal) const
Calculates the normal vector from the geometry element vertices.
void calcCentroid(const MFloat *const vertices, MFloat *centroid) const
Calculate the centroid of the geometry element vertices.
void getVertices(MFloat *vertices) const
Return the vertices of the geometry element.
void open(const MString &filename, const MString &projectName, MInt fileType=0, MPI_Comm mpiComm=MPI_COMM_WORLD, MBool rootOnlyHardwired=false)
Opens a file by passing the parameters to InfoOut_<xyz>FileBuffer::open(...).
Definition: infoout.cpp:975
This class is a ScratchSpace.
Definition: scratch.h:758
size_type size() const
Definition: scratch.h:302
T * getPointer() const
Deprecated: use begin() instead!
Definition: scratch.h:316
pointer p
Deprecated: use [] instead!
Definition: scratch.h:315
iterator end()
Definition: scratch.h:281
iterator begin()
Definition: scratch.h:273
MPI_Comm mpiComm() const
Return the MPI communicator used by this solver.
Definition: solver.h:380
const MInt m_solverId
a unique solver identifier
Definition: solver.h:90
Class that represents DG element collector.
MInt & polyDeg(const MInt id)
Accessor for polynomial degree.
MInt noNodesXD(const MInt id) const
Accessor for number of nodes XD (const version).
MFloat & invJacobian(const MInt id)
Accessor for inverse jacobian.
MInt & cellId(const MInt id)
Accessor for cell id.
MInt & surfaceIds(const MInt id, const MInt dir)
Accessor for surface ids.
MFloat & rightHandSide(const MInt id)
Accessor for right hand side.
MInt noNodes1D(const MInt id) const
Accessor for number of nodes 1D (const version).
Class that represents DG element collector.
MInt & hrefSurfaceIds(const MInt id, const MInt dir, const MInt pos)
Accessor for h-refined surface ids.
MInt & elementId(const MInt id)
Accessor for element id.
Class that represents DG element collector.
MInt & noNodes1D(const MInt srfcId)
Accessor for number of nodes 1D.
MInt & nghbrElementIds(const MInt srfcId, const MInt side)
Accessor for neighbor element ids.
MFloat & flux(const MInt srfcId)
Accessor for flux.
MInt & fineCellId(const MInt srfcId)
Accessor for fine cell id.
MInt & polyDeg(const MInt srfcId)
Accessor for polynomial degree.
MInt & orientation(const MInt srfcId)
Accessor for orientation.
size_type dim0() const
Return the size of dimension 0.
Definition: tensor.h:366
void clear()
Deletes the data (if internal storage was used) and resets dimensions to zero.
Definition: tensor.h:283
size_type dim1() const
Return the size of dimension 1.
Definition: tensor.h:380
void set(const T &value)
Initializes tensor to constant value.
Definition: tensor.h:271
size_type size() const
Returns the size of the array as product of all five dimensions (i.e. not the actual array size but t...
Definition: tensor.h:206
size_type resize(size_type n0, size_type n1=1, size_type n2=1, size_type n3=1, size_type n4=1)
Deletes the old data structure and creates a new one with the requested dimensions.
Definition: tensor.h:230
size_type dim2() const
Return the size of dimension 2.
Definition: tensor.h:394
MInt string2enum(MString theString)
This global function translates strings in their corresponding enum values (integer values)....
Definition: enums.cpp:20
@ DG_ADAPTIVE_GRADIENT
Definition: enums.h:336
@ DG_ADAPTIVE_TEST
Definition: enums.h:336
DgPolynomialType
Definition: enums.h:310
@ DG_SOURCETERMS
Definition: enums.h:385
@ DG_VARS
Definition: enums.h:385
@ DG_NODEVARS
Definition: enums.h:385
DgIntegrationMethod
Definition: enums.h:313
@ DG_INTEGRATE_GAUSS
Definition: enums.h:313
@ DG_INTEGRATE_GAUSS_LOBATTO
Definition: enums.h:313
@ DG_TIMEINTEGRATION_CARPENTER_4_5
Definition: enums.h:321
@ DG_TIMEINTEGRATION_TOULORGEC_3_7
Definition: enums.h:325
@ DG_TIMEINTEGRATION_TOULORGEC_4_8
Definition: enums.h:322
@ DG_TIMEINTEGRATION_TOULORGEF_4_8
Definition: enums.h:326
@ DG_TIMEINTEGRATION_NIEGEMANN_4_13
Definition: enums.h:324
@ DG_TIMEINTEGRATION_NIEGEMANN_4_14
Definition: enums.h:323
void mTerm(const MInt errorCode, const MString &location, const MString &message)
Definition: functions.cpp:29
const MString const MString & message
Definition: functions.h:37
MBool approx(const T &, const U &, const T)
Definition: functions.h:272
MInt ipow(MInt base, MInt exp)
Integer exponent function for non-negative exponents.
Definition: functions.h:317
MFloat wallTime()
Definition: functions.h:80
MInt globalTimeStep
void printAllocatedMemory(const MLong oldAllocatedBytes, const MString &solverName, const MPI_Comm comm)
Prints currently allocated memory.
MInt globalDomainId()
Return global domain id.
MBool g_splitMpiComm
MBool g_multiSolverGrid
InfoOutFile m_log
std::ostream cerr0
constexpr MLong IPOW2(MInt x)
constexpr MFloat FPOW2(MInt x)
int32_t MInt
Definition: maiatypes.h:62
uint32_t MUint
Definition: maiatypes.h:63
std::basic_string< char > MString
Definition: maiatypes.h:55
MInt noSolvers
Definition: maiatypes.h:73
double MFloat
Definition: maiatypes.h:52
int64_t MLong
Definition: maiatypes.h:64
bool MBool
Definition: maiatypes.h:58
int MPI_Isend(const void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request, const MString &name, const MString &varname)
same as MPI_Isend
int MPI_Send_init(const void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request, const MString &name, const MString &varname)
same as MPI_Send_init
int MPI_Allgatherv(const void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf, const int recvcounts[], const int displs[], MPI_Datatype recvtype, MPI_Comm comm, const MString &name, const MString &sndvarname, const MString &rcvvarname)
same as MPI_Allgatherv
int MPI_Waitsome(int incount, MPI_Request array_of_requests[], int *outcount, int array_of_indices[], MPI_Status array_of_statuses[], const MString &name)
same as MPI_Waitsome
int MPI_Type_commit(MPI_Datatype *datatype, const MString &name)
same as MPI_Type_commit
int MPI_Irecv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request *request, const MString &name, const MString &varname)
same as MPI_Irecv
int MPI_Recv_init(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request, const MString &name, const MString &varname)
same as MPI_Recv_init
int MPI_Type_free(MPI_Datatype *datatype, const MString &name)
same as MPI_Type_free
int MPI_Cancel(MPI_Request *request, const MString &name)
same as MPI_cancel
int MPI_Wait(MPI_Request *request, MPI_Status *status, const MString &name)
same as MPI_Wait
int MPI_Waitall(int count, MPI_Request *request, MPI_Status *status, const MString &name)
same as MPI_Waitall
int MPI_Type_create_hindexed(int count, const int array_of_solverlengths[], const MPI_Aint array_of_displacements[], MPI_Datatype oldtype, MPI_Datatype *newtype, const MString &name)
same as MPI_Type_create_hindexed
int MPI_Get_address(const void *location, MPI_Aint *address, const MString &name)
same as MPI_Get_address
int MPI_Allreduce(const void *sendbuf, void *recvbuf, int count, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm, const MString &name, const MString &sndvarname, const MString &rcvvarname)
same as MPI_Allreduce
int MPI_Request_free(MPI_Request *request, const MString &name)
same as MPI_Request_free
int MPI_Bcast(void *buffer, int count, MPI_Datatype datatype, int root, MPI_Comm comm, const MString &name, const MString &varname)
same as MPI_Bcast
int MPI_Allgather(const void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf, int recvcount, MPI_Datatype recvtype, MPI_Comm comm, const MString &name, const MString &sndvarname, const MString &rcvvarname)
same as MPI_Allgather
int MPI_Startall(int count, MPI_Request array_of_requests[], const MString &name)
same as MPI_Startall
void calcTrilinearInterpolation(MFloat *point, const MFloat *nodes, const MInt noNodes, const MInt noVars, const MFloat *u, MFloat *const out)
Calculates trilinear interpolation at given point (3D)
void calcLagrangeInterpolatingPolynomials(const MFloat x, const MInt polyDeg, const MFloat *nodes, const MFloat *wBary, MFloat *polynomials)
Calculates the values of the Lagrangian polynomials l_j for a given point x in [-1,...
void calcPolynomialInterpolationMatrix(MInt noNodesIn, const T nodesIn, MInt noNodesOut, const U nodesOut, const V wBary, W vandermonde)
Calculate the polynomial interpolation matrix (Vandermonde) to interpolate from one set of nodes to a...
void calcLinearInterpolationMatrix(const MInt noNodesIn, const T nodesIn, const MInt noNodesOut, const U nodesOut, V vandermonde)
Calculates the linear interpolation matrix (Vandermonde) to interpolate from one set of nodes to anot...
void calcBilinearInterpolation(MFloat *point, const MFloat *nodes, const MInt noNodes, const MInt noVars, const MFloat *u, MFloat *const out)
Calculates bilinear interpolation at given point (2D)
void calcMortarProjectionMatrixP(const MInt sourcePolyDeg, const MFloat *const sourceNodes, const MFloat *const sourceBaryWeights, const MInt targetPolyDeg, const MFloat *const targetNodes, MFloat *const matrix)
T linear(const T a, const T b, const T x)
Linear slope filter.
Definition: filter.h:83
void calcMortarProjectionMatrixPSBP(const MString sourceOp, const MString targetOp, const MInt sourceNoNodes, const MInt targetNoNodes, MFloat *const f, MFloat *const b)
Reads projection coefficients and stores them.
Namespace for auxiliary functions/classes.
PARALLELIO_DEFAULT_BACKEND ParallelIo
Definition: parallelio.h:292
Definition: contexttypes.h:19
static void init(FactoryType &factory)
Data & addData(const std::string &title_, T data_)
Definition: logtable.h:75
Group & addGroup(const std::string &title_)
Definition: logtable.h:159
MString buildString()
Definition: logtable.h:138
Data & addData(const std::string &title_, T data_)
Definition: logtable.h:111
maia::tensor::Tensor< MFloat > MFloatTensor
Definition: tensor.h:1101