MAIA bb96820c
Multiphysics at AIA
Loading...
Searching...
No Matches
acasolver.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 "acasolver.h"
9#include "INCLUDE/maiatypes.h"
10#include "IO/context.h"
11#include "MEMORY/scratch.h"
12#include "UTIL/functions.h"
13#include "UTIL/parallelfor.h"
14#include "UTIL/tensor.h"
15#include "acapostprocessing.hpp"
16#include "typetraits.h"
17
18using namespace maia;
19
20template <MInt nDim>
21const std::array<MString, 4> AcaSolver<nDim>::WINDOW::windowNames = {"none", "Hanning", "Hamming", "modified Hanning"};
22
23template <MInt nDim>
24AcaSolver<nDim>::AcaSolver(const MInt solverId, const MPI_Comm comm) : Solver(solverId, comm, true) {}
25
27template <MInt nDim>
29 TRACE();
30
31 // Initialize all solver-wide timers
32 initTimers();
33 // Input/output
34 setInputOutputProperties();
35 // Numerical method
36 setNumericalProperties();
37}
38
40template <MInt nDim>
42 TRACE();
43
44 // Invalidate timer ids
45 std::fill(m_timers.begin(), m_timers.end(), -1);
46
47 // Create timer group & timer for solver, and start the timer
48 NEW_TIMER_GROUP_NOCREATE(m_timerGroup, "AcaSolver (solverId = " + std::to_string(m_solverId) + ")");
49 NEW_TIMER_NOCREATE(m_timers[Timers::SolverType], "total object lifetime", m_timerGroup);
50 RECORD_TIMER_START(m_timers[Timers::SolverType]);
51
52 // Create regular solver-wide timers
53 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::Run], "run", m_timers[Timers::SolverType]);
54
55 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::InitParallelization], "initParallelization", m_timers[Timers::Run]);
56 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::ReadSurfaceData], "readSurfaceData", m_timers[Timers::Run]);
57
58 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::InitObservers], "initObservers", m_timers[Timers::Run]);
59 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::CalcSourceTerms], "calcSourceTerms", m_timers[Timers::Run]);
60 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::WindowAndTransformSources], "windowAndTransformSources",
61 m_timers[Timers::Run]);
62 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::CalcSurfaceIntegrals], "calcSurfaceIntegrals", m_timers[Timers::Run]);
63 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::CombineSurfaceIntegrals], "combineSurfaceIntegrals", m_timers[Timers::Run]);
64 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::BacktransformObservers], "backtransformObservers", m_timers[Timers::Run]);
65 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::SaveObserverSignals], "saveObserverSignals", m_timers[Timers::Run]);
66 NEW_SUB_TIMER_NOCREATE(m_timers[Timers::Postprocessing], "postprocessing", m_timers[Timers::Run]);
67}
68
73template <MInt nDim>
75 // 0) map timer ids for safety
76 std::vector<MInt> timerIds_;
77 timerIds_.reserve(11);
78 timerIds_.emplace_back(m_timers[Timers::Run]);
79 timerIds_.emplace_back(m_timers[Timers::InitParallelization]);
80 timerIds_.emplace_back(m_timers[Timers::ReadSurfaceData]);
81 timerIds_.emplace_back(m_timers[Timers::InitObservers]);
82 timerIds_.emplace_back(m_timers[Timers::CalcSourceTerms]);
83 timerIds_.emplace_back(m_timers[Timers::WindowAndTransformSources]);
84 timerIds_.emplace_back(m_timers[Timers::CalcSurfaceIntegrals]);
85 timerIds_.emplace_back(m_timers[Timers::CombineSurfaceIntegrals]);
86 timerIds_.emplace_back(m_timers[Timers::BacktransformObservers]);
87 timerIds_.emplace_back(m_timers[Timers::SaveObserverSignals]);
88 timerIds_.emplace_back(m_timers[Timers::Postprocessing]);
89 const MInt noTimers = timerIds_.size();
90 // 1) fill buffer with local timer values
91 std::vector<MFloat> timerValues_;
92 timerValues_.reserve(noTimers);
93 for(MInt i = 0; i < noTimers; i++) {
94 timerValues_.emplace_back(RETURN_TIMER_TIME(timerIds_[i]));
95 }
96 // 2) collect values from all ranks and use max value
97 MPI_Allreduce(MPI_IN_PLACE, timerValues_.data(), noTimers, maia::type_traits<MFloat>::mpiType(), MPI_MAX, mpiComm(),
98 AT_, "MPI_IN_PLACE", "timerValues_");
99 for(MInt i = 0; i < noTimers; i++) {
100 SET_RECORD(timerIds_[i], timerValues_[i]);
101 }
102}
103
104
106template <MInt nDim>
108 TRACE();
109
117 m_inputDir = Context::getSolverProperty<MString>("inputDir", m_solverId, AT_);
118 m_inputDir = testcaseDir() + m_inputDir;
119
128 m_observerFileName = "";
129 m_observerFileName = Context::getSolverProperty<MString>("observerFileName", m_solverId, AT_, &m_observerFileName);
130
139 m_generateObservers = false;
140 m_generateObservers = Context::getSolverProperty<MBool>("generateObservers", m_solverId, AT_, &m_generateObservers);
141
142 TERMM_IF_COND(m_observerFileName == "" && m_generateObservers == false,
143 "No observer file provided and generation of observers disabled.");
144
145 // Prefix added to all output files
154 m_outputFilePrefix = "";
155 m_outputFilePrefix = Context::getSolverProperty<MString>("outputFilePrefix", m_solverId, AT_, &m_outputFilePrefix);
156
157 // Number of surfaces
166 m_noSurfaces = 0;
167 m_noSurfaces = Context::getSolverProperty<MInt>("noSurfaces", m_solverId, AT_, &m_noSurfaces);
168
169 // Surface ids
170 if(Context::propertyExists("surfaceIds", m_solverId)) {
171 const MInt noGivenSrfcIds = Context::propertyLength("surfaceIds", m_solverId);
172 if(noSurfaces() != 0 && noSurfaces() != noGivenSrfcIds) {
173 std::cerr << "WARNING: Mismatch between properties 'noSurfaces' and size of surfaceIds" << std::endl;
174 }
175 m_noSurfaces = noGivenSrfcIds;
176 TERMM_IF_COND(m_noSurfaces <= 0, "Number of surfaces (noSurfaces) needs to be > 0");
177 m_surfaceIds.resize(noSurfaces());
178 for(MInt id = 0; id < noSurfaces(); id++) {
188 m_surfaceIds[id] = Context::getSolverProperty<MInt>("surfaceIds", m_solverId, AT_, id);
189 }
190 } else {
191 TERMM_IF_COND(m_noSurfaces <= 0, "Number of surfaces (noSurfaces) needs to be > 0");
192 m_surfaceIds.resize(noSurfaces());
193 for(MInt id = 0; id < noSurfaces(); id++) {
194 m_surfaceIds[id] = id;
195 }
196 }
197
198 // Use merged input file or multiple input files
208 m_useMergedInputFile = true;
209 m_useMergedInputFile =
210 Context::getSolverProperty<MBool>("useMergedInputFile", m_solverId, AT_, &m_useMergedInputFile);
211 if(m_useMergedInputFile == false) {
212 // inputFileIndexStart and inputFileIndexEnd
220 m_inputFileIndexStart = Context::getSolverProperty<MInt>("inputFileIndexStart", m_solverId, AT_);
228 m_inputFileIndexEnd = Context::getSolverProperty<MInt>("inputFileIndexEnd", m_solverId, AT_);
229 TERMM_IF_COND(m_inputFileIndexStart > m_inputFileIndexEnd,
230 "'inputFileIndexStart' must not be greater than 'm_inputFileIndexEnd'.");
231 }
232
242 m_noSamples = -1;
243 m_noSamples = Context::getSolverProperty<MInt>("acaNoSamples", m_solverId, AT_, &m_noSamples);
244
253 m_sampleStride = 1;
254 m_sampleStride = Context::getSolverProperty<MInt>("acaSampleStride", m_solverId, AT_, &m_sampleStride);
255
264 m_sampleOffset = 0;
265 m_sampleOffset = Context::getSolverProperty<MInt>("acaSampleOffset", m_solverId, AT_, &m_sampleOffset);
266
276 m_allowMultipleSurfacesPerRank = Context::getSolverProperty<MBool>("allowMultipleSurfacesPerRank", m_solverId, AT_,
277 &m_allowMultipleSurfacesPerRank);
278
287 m_generateSurfaceData = false;
288 m_generateSurfaceData =
289 Context::getSolverProperty<MBool>("generateSurfaceData", m_solverId, AT_, &m_generateSurfaceData);
290
304 m_acaResultsNondimMode = 0;
305 m_acaResultsNondimMode =
306 Context::getSolverProperty<MInt>("acaResultsNondimMode", m_solverId, AT_, &m_acaResultsNondimMode);
307
316 m_storeSurfaceData = false;
317 m_storeSurfaceData = Context::getSolverProperty<MBool>("storeSurfaceData", m_solverId, AT_, &m_storeSurfaceData);
318
319 // Read in the postprocessing operations
320 m_noPostprocessingOps = 0;
321 if(Context::propertyExists("postprocessingOps", m_solverId)) {
322 m_noPostprocessingOps = Context::propertyLength("postprocessingOps", m_solverId);
323 m_postprocessingOps.resize(m_noPostprocessingOps, -1);
324 }
325 for(MInt i = 0; i < m_noPostprocessingOps; i++) {
342 m_postprocessingOps[i] = Context::getSolverProperty<MInt>("postprocessingOps", m_solverId, AT_, i);
343 }
344
354 m_acaPostprocessingMode = false;
355 m_acaPostprocessingMode =
356 Context::getSolverProperty<MBool>("acaPostprocessingMode", m_solverId, AT_, &m_acaPostprocessingMode);
357
358 if(m_acaPostprocessingMode) {
359 TERMM_IF_COND(m_noPostprocessingOps == 0, "acaPostprocessingMode is enabled but noPostprocessingOps is zero.");
360
368 m_acaPostprocessingFile = Context::getSolverProperty<MString>("acaPostprocessingFile", m_solverId, AT_);
369 }
370
380 const MString propNameSymOrigin = "symmetryOrigin";
390 const MString propNameSymNormal = "symmetryNormal";
391 if(!(Context::propertyExists(propNameSymOrigin, m_solverId)
392 || Context::propertyExists(propNameSymNormal, m_solverId)))
393 return;
394 const MInt propLength_symOrign = Context::propertyLength(propNameSymOrigin, m_solverId);
395 const MInt propLength_symNormal = Context::propertyLength(propNameSymNormal, m_solverId);
396 TERMM_IF_COND(propLength_symOrign % nDim != 0, propNameSymOrigin + " has to be of a length of multiple nDim.");
397 TERMM_IF_COND(propLength_symNormal % nDim != 0, propNameSymNormal + " has to be of a length of multiple nDim.");
398 const MInt noSym = propLength_symOrign / nDim;
399 TERMM_IF_COND(noSym != propLength_symNormal / nDim,
400 propNameSymOrigin + " and " + propNameSymNormal + " have to be of the same length.");
401 // TODO labels:ACA Make more than one symmetry possible?
402 TERMM_IF_COND(noSym > 1, "So far not more than 1 symmetry BC possible.");
403 m_symBc.resize(noSym);
404 for(MInt i = 0; i < noSym; i++) {
405 MFloat lengthSymNormal = 0.0;
406 for(MInt d = 0; d < nDim; d++) {
407 m_symBc[i].origin[d] = Context::getSolverProperty<MFloat>(propNameSymOrigin, m_solverId, AT_, i * nDim + d);
408 m_symBc[i].normal[d] = Context::getSolverProperty<MFloat>(propNameSymNormal, m_solverId, AT_, i * nDim + d);
409 lengthSymNormal += POW2(m_symBc[i].normal[d]);
410 }
411 lengthSymNormal = sqrt(lengthSymNormal);
412 for(MInt d = 0; d < nDim; d++) {
413 m_symBc[i].normal[d] /= lengthSymNormal; // normalized normal vector
414 }
415 }
416}
417
418
420template <MInt nDim>
422 TRACE();
423
436 const MString methodName = Context::getSolverProperty<MString>("acousticMethod", solverId(), AT_);
437 m_acousticMethod = -1;
438 m_acousticMethod = string2enum(methodName);
439 m_inputVars.clear();
440
441 switch(m_acousticMethod) {
442 case FWH_METHOD:
443 // Set number of (complex) variables
444 m_noVariables = FWH::noVars;
445 m_noComplexVariables = FWH::noComplexVars;
446
447 // Store names of input variables with corresponding storage position in m_surfaceData
448 m_inputVars["u"] = FWH::U;
449 m_inputVars["v"] = FWH::V;
450 if constexpr(nDim == 3) {
451 m_inputVars["w"] = FWH::W;
452 }
453 m_inputVars["rho"] = FWH::RHO;
454 m_inputVars["p"] = FWH::P;
455
456 break;
457 case FWH_APE_METHOD:
458 // Set number (complex) variables
459 m_noVariables = FWH_APE::noVars;
460 m_noComplexVariables = FWH_APE::noComplexVars;
461
462 // Store names of input variables with corresponding storage position in m_surfaceData
463 m_inputVars["u"] = FWH_APE::UP;
464 m_inputVars["v"] = FWH_APE::VP;
465 if constexpr(nDim == 3) {
466 m_inputVars["w"] = FWH_APE::WP;
467 }
468 m_inputVars["p"] = FWH_APE::PP;
469 // APE = Acoustic Perturbed Equation
470
471 break;
472 default:
473 TERMM(1, "Invalid acoustic extrapolation method: '" + std::to_string(m_acousticMethod) + "'");
474 break;
475 }
476
477 // FWH_Farassat_1A time domain formulation
478 if(string2enum(solverMethod()) == MAIA_FWH_TIME) m_noVariables = FWH_TIME::noVars;
479
480 // Use observer time shift
497 m_fwhTimeShiftType = 0;
498 m_fwhTimeShiftType = Context::getSolverProperty<MInt>("fwhTimeShiftType", m_solverId, AT_, &m_fwhTimeShiftType);
499
500 // Set mean variables
512 m_fwhMeanStateType = 0;
513 m_fwhMeanStateType = Context::getSolverProperty<MInt>("fwhMeanStateType", m_solverId, AT_, &m_fwhMeanStateType);
514
515 // Type used at windowing function: default setting is 0 = NONE
533 m_windowType = 0;
534 m_windowType = Context::getSolverProperty<MInt>("windowType", m_solverId, AT_, &m_windowType);
535
551 m_transformationType = 1;
552 m_transformationType = Context::getSolverProperty<MInt>("transformationType", m_solverId, AT_, &m_transformationType);
553
554 // Surface weighting factor for surface averaging (default 1.0)
555 m_surfaceWeightingFactor.resize(noSurfaces(), 1.0);
556 if(Context::propertyExists("surfaceWeightingFactor")) {
557 for(MInt sId = 0; sId < noSurfaces(); sId++) {
566 const MFloat weightingFactor = Context::getSolverProperty<MFloat>("surfaceWeightingFactor", solverId(), AT_, sId);
567 TERMM_IF_COND(weightingFactor < 0.0 || weightingFactor > 1.0,
568 "Error: surface weighting factor should be in range (0.0,1.0], is: "
569 + std::to_string(weightingFactor));
570 m_surfaceWeightingFactor[sId] = weightingFactor;
571
572 m_log << "Surface weighting factor for surface #" << m_surfaceIds[sId] << ": " << m_surfaceWeightingFactor[sId]
573 << std::endl;
574 }
575 } else {
576 m_log << "Surface weighting not enabled." << std::endl;
577 }
578
579 m_log << "Mach vector: ";
580 MFloat machSq = 0.0;
581 for(MInt i = 0; i < nDim; i++) {
582 m_MaVec[i] = 0.0;
591 m_MaVec[i] = Context::getSolverProperty<MFloat>("Ma", m_solverId, AT_, &m_MaVec[i], i);
592 m_log << m_MaVec[i] << " ";
593 machSq += POW2(m_MaVec[i]);
594 }
595 const MFloat Mach = sqrt(machSq);
596 m_Ma = Mach;
597
606 if(Context::propertyExists("MaDim", m_solverId)) {
607 m_MaDim = Context::getSolverProperty<MFloat>("MaDim", m_solverId, AT_);
608 } else {
609 m_MaDim = m_Ma;
610 }
611 m_log << "; Ma = " << m_Ma << "; Ma_dim = " << m_MaDim << std::endl;
612
613 // Coordinate system transformation
614 /* Project the vectors to the flow direction U_inf = (U_inf, 0, 0) to apply FWH formulations.
615 * Used in functions: transformCoordinatesToFlowDirection2D, transformCoordinatesToFlowDirection3D
616 * transformBackToOriginalCoordinate2D, transformBackToOriginalCoordinate3D
617 */
618 MFloat theta = 0.0;
619 MFloat alpha = 0.0;
620 if constexpr(nDim == 3) {
621 if(!approx(m_MaVec[2], 0.0, MFloatEps) && !approx(m_MaVec[0], 0.0, MFloatEps)) {
622 theta = atan2(m_MaVec[2], m_MaVec[0]);
623 }
624 if(!approx(m_MaVec[1], 0.0, MFloatEps)) {
625 alpha = asin(m_MaVec[1] / m_Ma);
626 }
627 m_transformationMatrix[0] = cos(alpha) * cos(theta);
628 m_transformationMatrix[1] = sin(alpha);
629 m_transformationMatrix[2] = cos(alpha) * sin(theta);
630 m_transformationMatrix[3] = -sin(alpha) * cos(theta);
631 m_transformationMatrix[4] = cos(alpha);
632 m_transformationMatrix[5] = -sin(alpha) * sin(theta); // "+" in Locard's formulation
633 m_transformationMatrix[6] = -sin(theta);
634 m_transformationMatrix[7] = 0.0;
635 m_transformationMatrix[8] = cos(theta);
636 } else if constexpr(nDim == 2) {
637 if(!approx(m_MaVec[1], 0.0, MFloatEps) && !approx(m_MaVec[0], 0.0, MFloatEps)) {
638 theta = atan2(m_MaVec[1], m_MaVec[0]);
639 }
640 m_transformationMatrix[0] = cos(theta);
641 m_transformationMatrix[1] = sin(theta);
642 m_transformationMatrix[2] = -sin(theta);
643 m_transformationMatrix[3] = cos(theta);
644 }
645 MFloat flowAngle = std::sqrt(theta * theta + alpha * alpha);
646 if(approx(flowAngle, 0.0, MFloatEps)) m_zeroFlowAngle = true;
647
648 if(m_generateSurfaceData) {
665 m_sourceType = -1;
666 m_sourceType = Context::getSolverProperty<MInt>("sourceType", m_solverId, AT_, &m_sourceType);
667
668 m_sourceParameters.perturbed = (m_acousticMethod == FWH_METHOD) ? false : true;
669
670 // Mach number from -x direction
671 m_sourceParameters.Ma = Mach;
672 constexpr MFloat c0 = 1.0; // per definition
673
682 m_sourceParameters.rho0 = 1.0;
683 m_sourceParameters.rho0 = Context::getSolverProperty<MFloat>("rho0", m_solverId, AT_, &m_sourceParameters.rho0);
684
685 // values
694 m_omega_factor = (nDim == 2) ? 4.0 : 4.0 / 46.0;
695 m_omega_factor = Context::getSolverProperty<MFloat>("omega_factor", m_solverId, AT_, &m_omega_factor);
696
705 m_sourceParameters.omega = m_omega_factor * PI * c0;
706 m_sourceParameters.omega = Context::getSolverProperty<MFloat>("omega", m_solverId, AT_, &m_sourceParameters.omega);
707
716 m_sourceParameters.beta = sqrt(1 - Mach * Mach);
717 m_sourceParameters.beta = Context::getSolverProperty<MFloat>("beta", m_solverId, AT_, &m_sourceParameters.beta);
718
719 // Amplitude
720 m_sourceParameters.amplitude = (nDim == 2 ? 0.0001 : 0.01) * c0; // [m^2/s]
721
722 {
723 std::stringstream ss;
724 ss << "Source parameters" << std::endl;
725 ss << " perturbed: " << m_sourceParameters.perturbed << std::endl;
726 ss << " omega: " << m_sourceParameters.omega << std::endl;
727 ss << " rho0: " << m_sourceParameters.rho0 << std::endl;
728 ss << " beta: " << m_sourceParameters.beta << std::endl;
729 ss << " amplitude: " << m_sourceParameters.amplitude << std::endl;
730 ss << " Ma: " << m_sourceParameters.Ma << std::endl;
731 m_log << ss.str();
732 }
733
742 m_noSamples = -1;
743 m_noSamples = Context::getSolverProperty<MInt>("generateSurfaceDataNoSamples", m_solverId, AT_);
744 m_totalNoSamples = m_noSamples;
745 m_sampleStride = 1;
746 m_sampleOffset = 0;
747
748 // If uneven number of samples, take on less
749 if(m_noSamples % 2 > 0) {
750 m_noSamples = m_noSamples - 1;
751 if(domainId() == 0) {
752 std::cout << "Note: Number of Samples was uneven which could lead to mistakes. This is why the "
753 "new number of Samples is one less."
754 << std::endl;
755 }
756 }
757
766 MFloat noPeriods = 1.0;
767 noPeriods = Context::getSolverProperty<MFloat>("noPeriods", m_solverId, AT_, &noPeriods);
768 TERMM_IF_COND(noPeriods < 1.0, "The number of periods is below 1!");
769
778 m_dt = noPeriods * 2.0 * PI / (noSamples() * m_sourceParameters.omega);
779 m_dt = Context::getSolverProperty<MFloat>("timeStepSize", m_solverId, AT_, &m_dt);
780 const MFloat freq_max = m_sourceParameters.omega / (2 * PI);
781 // Check Nyquist criterion afterwards
782 TERMM_IF_COND(m_dt >= 1.0 / (2.0 * freq_max), "Nyquist criterions is not met!");
783
784 // COUT the max frequency and the sampling rate to show that the Nyquist criterion is met
785 if(domainId() == 0) {
786 std::cout << " >> NOTE: The maximum frequency is f_max = omega / (2*pi) = " << freq_max
787 << ". The Nyquist criterion is ensured ( m_dt < 1 /(2*freq_max) ), since..." << std::endl;
788 std::cout << " >> m_dt = " << m_dt << " < " << 1.0 / (2.0 * freq_max) << " = 1 / (2*freq_max) <<" << std::endl;
789 }
790 } // End of "if m_generateSurfaceData"
791
792 if(domainId() == 0) {
793 std::cout << "Acoustic extrapolation method: " << methodName << std::endl;
794 std::cout << " * noVars = " << m_noVariables << std::endl;
795 std::cout << " * noComplexVars = " << m_noComplexVariables << std::endl;
796
797 std::cout << " * Input variables:" << std::endl;
798 for(auto& var : m_inputVars) {
799 std::cout << " * " << var.first << ", position " << var.second << std::endl;
800 }
801 }
802}
803
804
806template <MInt nDim>
808 TRACE();
809 RECORD_TIMER_START(m_timers[Timers::Run]);
810
811 // Distribute surface elements among ranks and create separate communicators etc.
812 initParallelization();
813
814 initConversionFactors();
815
816 // read in data of all surfaces
817 // Note: for validating/testing instead of loading the data from disk you can generate the
818 // analytical input data
819 readSurfaceData();
820
821 // read in observer points or generate the according to some properties if specified
822 initObserverPoints();
823
824 if(!m_acaPostprocessingMode) {
825 // Compute source terms depending on method
826 calcSourceTerms();
827
828 // Compute observer pressure signal by using time or frequency FW-H formulation
829 if(string2enum(solverMethod()) == MAIA_FWH_FREQUENCY) {
830 // apply window to source terms and transform
831 windowAndTransformSources();
832
833 calcFrequencies();
834
835 // Calculate the observer signals in frequency domain
836 calculateObserverInFrequency();
837
838 // backtransformation of local observer signal from frequency into time domain
839 backtransformObservers();
840 } else if(string2enum(solverMethod()) == MAIA_FWH_TIME) {
841 // Surface Integration
842 calculateObserverInTime();
843
844 calcFrequencies();
845
846 // Windowing and FFT of the observer data
847 windowAndTransformObservers();
848 }
849
850 // change dimensions from ACA to desired output format
851 changeDimensionsObserverData();
852
853 // output of observer signals (frequency and time domain)
854 saveObserverSignals();
855
856 // perform additional post-processing operations with the observer signals, e.g., compute
857 // RMS-pressure, average over observers, ..., and output the data
858 postprocessObserverSignals();
859
860 // Calculates the accuracy, the difference between analytic and computed solution. Can be only be
861 // used with analytical data at the moment.
862 // computeAccuracy();
863 } else {
864 loadObserverSignals();
865
866 // Postprocessing with loaded observer data
867 postprocessObserverSignals();
868 }
869
870 RECORD_TIMER_STOP(m_timers[Timers::Run]);
871
872 m_isFinished = true;
873 return true;
874}
875
876
878template <MInt nDim>
880 TRACE();
881 RECORD_TIMER_START(m_timers[Timers::CalcSourceTerms]);
882 printMessage("- Calculate source terms");
883
884 // Note: totalNoSurfaceElements() returns the local, total number of elements for a surface
885 // (may differ from domain to domain)
886
887 if(string2enum(solverMethod()) == MAIA_FWH_FREQUENCY) {
888 m_log << "Using Frequency-Domain FW-H Formulation" << std::endl;
889 for(MInt i = 0; i < totalNoSurfaceElements(); i++) {
890 calcSourceTermsFwhFreq(i);
891 }
892 } else if(string2enum(solverMethod()) == MAIA_FWH_TIME) {
893 m_log << "Using Farassat 1A Time-Domain FW-H Formulation" << std::endl;
894 for(MInt i = 0; i < totalNoSurfaceElements(); i++) {
895 calcSourceTermsFwhTime(i);
896 }
897 } else {
898 TERMM(1, "Unknown solverMethod");
899 }
900
901 RECORD_TIMER_STOP(m_timers[Timers::CalcSourceTerms]);
902}
903
908template <MInt nDim>
910 TRACE();
911
912 // Get pointer to normal vector
913 const MFloat* const normal = &m_surfaceData.surfaceNormal(segmentId);
914 // First two normal vector components (2D)
915 const MFloat dfdx = normal[0];
916 const MFloat dfdy = normal[1];
917 // Mach numbers (2D)
918 const MFloat Max = m_MaVec[0];
919 const MFloat May = m_MaVec[1];
920
921 const MFloat c_inf = 1.0; // by definition
922 const MFloat rho_inf = 1.0; // by definition
923 const MFloat p_inf = rho_inf * (c_inf * c_inf) / m_gamma;
924 const MFloat noSamplesInv = 1.0 / noSamples();
925
926 if constexpr(nDim == 3) {
927 // Third normal vector component for 3D
928 const MFloat dfdz = normal[2];
929 // Third Mach number for 3D
930 const MFloat Maz = m_MaVec[2];
931
932 // Compute mean velocities
933 MFloat uMean = 0.0;
934 MFloat vMean = 0.0;
935 MFloat wMean = 0.0;
936 if(m_acousticMethod == FWH_METHOD) {
937 switch(m_fwhMeanStateType) {
938 case 0: { // Default: use infnity properties to compute u'
939 uMean = m_MaVec[0];
940 vMean = m_MaVec[1];
941 wMean = m_MaVec[2];
942 break;
943 }
944 case 1: { // Use mean velocities to compute u'
945 for(MInt t = 0; t < noSamples(); t++) {
946 uMean += m_surfaceData.variables(segmentId, FWH::U, t);
947 vMean += m_surfaceData.variables(segmentId, FWH::V, t);
948 wMean += m_surfaceData.variables(segmentId, FWH::W, t);
949 }
950 uMean *= noSamplesInv;
951 vMean *= noSamplesInv;
952 wMean *= noSamplesInv;
953 break;
954 }
955 default: {
956 TERMM(1, "fwh mean state type not implemented");
957 break;
958 }
959 }
960 }
961
962 // Time loop over all samples for 3D
963 maia::parallelFor<false>(0, noSamples(), [=](MInt t) {
964 // Get required variables (u', v', w', p, rho)
965 std::vector<MInt> VarDim;
966 MFloat up = 0.0;
967 MFloat vp = 0.0;
968 MFloat wp = 0.0;
969 MFloat p = 0.0;
970 MFloat rho = 0.0;
971 if(m_acousticMethod == FWH_METHOD) {
972 VarDim = {FWH::FX, FWH::FY, FWH::FZ, FWH::Q};
973 up = m_surfaceData.variables(segmentId, FWH::U, t) - uMean;
974 vp = m_surfaceData.variables(segmentId, FWH::V, t) - vMean;
975 wp = m_surfaceData.variables(segmentId, FWH::W, t) - wMean;
976 p = m_surfaceData.variables(segmentId, FWH::P, t);
977 rho = m_surfaceData.variables(segmentId, FWH::RHO, t);
978 } else if(m_acousticMethod == FWH_APE_METHOD) {
979 VarDim = {FWH_APE::FX, FWH_APE::FY, FWH_APE::FZ, FWH_APE::Q};
980 up = m_surfaceData.variables(segmentId, FWH_APE::UP, t);
981 vp = m_surfaceData.variables(segmentId, FWH_APE::VP, t);
982 wp = m_surfaceData.variables(segmentId, FWH_APE::WP, t);
983 p = m_surfaceData.variables(segmentId, FWH_APE::PP, t) + p_inf;
984 rho = m_surfaceData.variables(segmentId, FWH_APE::PP, t) + rho_inf;
985 }
986
987 const MFloat f1 = (up + Max) * dfdx + (vp + May) * dfdy + (wp + Maz) * dfdz;
988 const MFloat f2 = Max * dfdx + May * dfdy + Maz * dfdz;
989
990 // Calculation of source terms (3D) Fx, Fy, Fz and Q for each sample.
991 m_surfaceData.variables(segmentId, VarDim[0], t) = p * dfdx + rho * (up - Max) * f1 + Max * f2;
992 m_surfaceData.variables(segmentId, VarDim[1], t) = p * dfdy + rho * (vp - May) * f1 + May * f2;
993 m_surfaceData.variables(segmentId, VarDim[2], t) = p * dfdz + rho * (wp - Maz) * f1 + Maz * f2;
994 m_surfaceData.variables(segmentId, VarDim[3], t) = rho * f1 - f2;
995 });
996 } else {
997 // Compute mean velocities
998 MFloat uMean = 0.0;
999 MFloat vMean = 0.0;
1000 if(m_acousticMethod == FWH_METHOD) {
1001 switch(m_fwhMeanStateType) {
1002 case 0: { // Default: use infnity properties to compute u'
1003 uMean = m_MaVec[0];
1004 vMean = m_MaVec[1];
1005 break;
1006 }
1007 case 1: { // Use mean velocities to compute u'
1008 for(MInt t = 0; t < noSamples(); t++) {
1009 uMean += m_surfaceData.variables(segmentId, FWH::U, t);
1010 vMean += m_surfaceData.variables(segmentId, FWH::V, t);
1011 }
1012 uMean = uMean * noSamplesInv;
1013 vMean = vMean * noSamplesInv;
1014 break;
1015 }
1016 default: {
1017 TERMM(1, "fwh mean state type not implemented");
1018 break;
1019 }
1020 }
1021 }
1022
1023 // Time loop over all samples for 2D
1024 maia::parallelFor<false>(0, noSamples(), [=](MInt t) {
1025 // Get required variables (u', v', p, rho)
1026 std::vector<MInt> VarDim;
1027 MFloat up = 0.0;
1028 MFloat vp = 0.0;
1029 MFloat p = 0.0;
1030 MFloat rho = 0.0;
1031 if(m_acousticMethod == FWH_METHOD) {
1032 VarDim = {FWH::FX, FWH::FY, FWH::Q};
1033 up = m_surfaceData.variables(segmentId, FWH::U, t) - uMean;
1034 vp = m_surfaceData.variables(segmentId, FWH::V, t) - vMean;
1035 p = m_surfaceData.variables(segmentId, FWH::P, t);
1036 rho = m_surfaceData.variables(segmentId, FWH::RHO, t);
1037 } else if(m_acousticMethod == FWH_APE_METHOD) {
1038 VarDim = {FWH_APE::FX, FWH_APE::FY, FWH_APE::Q};
1039 up = m_surfaceData.variables(segmentId, FWH_APE::UP, t);
1040 vp = m_surfaceData.variables(segmentId, FWH_APE::VP, t);
1041 p = m_surfaceData.variables(segmentId, FWH_APE::PP, t) + p_inf;
1042 rho = m_surfaceData.variables(segmentId, FWH_APE::PP, t) + rho_inf;
1043 }
1044
1045 const MFloat f1 = (up + Max) * dfdx + (vp + May) * dfdy;
1046 const MFloat f2 = Max * dfdx + May * dfdy;
1047
1048 // Calculation of source terms (2D) Fx, Fy and Q for each sample.
1049 m_surfaceData.variables(segmentId, VarDim[0], t) = p * dfdx + rho * (up - Max) * f1 + Max * f2;
1050 m_surfaceData.variables(segmentId, VarDim[1], t) = p * dfdy + rho * (vp - May) * f1 + May * f2;
1051 m_surfaceData.variables(segmentId, VarDim[2], t) = rho * f1 - f2;
1052 });
1053 }
1054}
1055
1061template <MInt nDim>
1063 TRACE();
1064
1065 // Get pointer to normal vector
1066 const MFloat* const normal = &m_surfaceData.surfaceNormal(segmentId);
1067 const MFloat c_inf = 1.0; // by definition
1068 const MFloat rho_inf = 1.0; // by definition
1069 const MFloat p_inf = rho_inf * (c_inf * c_inf) / m_gamma;
1070 const MFloat noSamplesInv = 1.0 / noSamples();
1071
1072 if constexpr(nDim == 3) {
1073 // Compute mean variables
1074 MFloat uMean = 0.0;
1075 MFloat vMean = 0.0;
1076 MFloat wMean = 0.0;
1077 MFloat pMean = 0.0;
1078 if(m_acousticMethod == FWH_METHOD) {
1079 switch(m_fwhMeanStateType) {
1080 case 0: { // Default: use infnity properties to compute u'
1081 uMean = m_MaVec[0];
1082 vMean = m_MaVec[1];
1083 wMean = m_MaVec[2];
1084 pMean = p_inf;
1085 break;
1086 }
1087 case 1: { // Use mean velocities to compute u'
1088 for(MInt t = 0; t < noSamples(); t++) {
1089 uMean += m_surfaceData.variables(segmentId, FWH::U, t);
1090 vMean += m_surfaceData.variables(segmentId, FWH::V, t);
1091 wMean += m_surfaceData.variables(segmentId, FWH::W, t);
1092 pMean += m_surfaceData.variables(segmentId, FWH::P, t);
1093 }
1094 uMean *= noSamplesInv;
1095 vMean *= noSamplesInv;
1096 wMean *= noSamplesInv;
1097 pMean *= noSamplesInv;
1098 break;
1099 }
1100 default: {
1101 TERMM(1, "fwh mean state type not implemented");
1102 break;
1103 }
1104 }
1105 }
1106
1107 // Set surface geometry properties
1108 // TODO: This assumes a stationary sampling surface! Impliment the version for moving surface...
1109 MFloat nx = normal[0];
1110 MFloat ny = normal[1];
1111 MFloat nz = normal[2];
1112 MFloat surfVX = -m_MaVec[0];
1113 MFloat surfVY = -m_MaVec[1];
1114 MFloat surfVZ = -m_MaVec[2];
1115
1116 maia::parallelFor<false>(0, noSamples(), [=](MInt tau) {
1117 // Get surface (f=0) velocity --> based on the ABSOLUTE frame of reference
1118 m_surfaceData.variables(segmentId, FWH_TIME::surfMX, tau) = surfVX;
1119 m_surfaceData.variables(segmentId, FWH_TIME::surfMY, tau) = surfVY;
1120 m_surfaceData.variables(segmentId, FWH_TIME::surfMZ, tau) = surfVZ;
1121
1122 // Get primative variables --> based on the ABSOLUTE frame of reference
1123 MFloat up = 0.0;
1124 MFloat vp = 0.0;
1125 MFloat wp = 0.0;
1126 MFloat pp = 0.0;
1127 MFloat rho = 0.0;
1128 if(m_acousticMethod == FWH_METHOD) {
1129 up = m_surfaceData.variables(segmentId, FWH::U, tau) - uMean;
1130 vp = m_surfaceData.variables(segmentId, FWH::V, tau) - vMean;
1131 wp = m_surfaceData.variables(segmentId, FWH::W, tau) - wMean;
1132 pp = m_surfaceData.variables(segmentId, FWH::P, tau) - pMean;
1133 rho = m_surfaceData.variables(segmentId, FWH::RHO, tau);
1134 } else if(m_acousticMethod == FWH_APE_METHOD) {
1135 up = m_surfaceData.variables(segmentId, FWH_APE::UP, tau);
1136 vp = m_surfaceData.variables(segmentId, FWH_APE::VP, tau);
1137 wp = m_surfaceData.variables(segmentId, FWH_APE::WP, tau);
1138 pp = m_surfaceData.variables(segmentId, FWH_APE::PP, tau);
1139 rho = rho_inf + pp; // = rho_inf + p'/(c_inf * c_inf)
1140 }
1141
1142 // Compute sources
1143 const MFloat un = up * nx + vp * ny + wp * nz;
1144 const MFloat vn = surfVX * nx + surfVY * ny + surfVZ * nz;
1145 const MFloat deltaUn = un - vn;
1146
1147 m_surfaceData.variables(segmentId, FWH_TIME::srcUn, tau) = vn + rho / rho_inf * deltaUn;
1148 m_surfaceData.variables(segmentId, FWH_TIME::srcLX, tau) = pp * nx + rho * up * deltaUn;
1149 m_surfaceData.variables(segmentId, FWH_TIME::srcLY, tau) = pp * ny + rho * vp * deltaUn;
1150 m_surfaceData.variables(segmentId, FWH_TIME::srcLZ, tau) = pp * nz + rho * wp * deltaUn;
1151
1152 // Transform the source terms to the flow direction coordinate
1153 transformCoordinatesToFlowDirection3D(&m_surfaceData.variables(segmentId, FWH_TIME::surfMX, tau),
1154 &m_surfaceData.variables(segmentId, FWH_TIME::surfMY, tau),
1155 &m_surfaceData.variables(segmentId, FWH_TIME::surfMZ, tau));
1156 transformCoordinatesToFlowDirection3D(&m_surfaceData.variables(segmentId, FWH_TIME::srcLX, tau),
1157 &m_surfaceData.variables(segmentId, FWH_TIME::srcLY, tau),
1158 &m_surfaceData.variables(segmentId, FWH_TIME::srcLZ, tau));
1159
1160 /*// Debugging
1161 MFloat* surfCoordinates = &m_surfaceData.surfaceCoordinates(segmentId);
1162 if(segmentId == 0 && tau == 0) {
1163 m_log << "tau = " << tau
1164 << ", surfCoordinates[0] = " << surfCoordinates[0]
1165 << ", surfCoordinates[1] = " << surfCoordinates[1]
1166 << ", surfCoordinates[2] = " << surfCoordinates[2]
1167 << ", nx = " << normal[0]
1168 << ", ny = " << normal[1]
1169 << ", nz = " << normal[2]
1170 << ", surfVX = " << surfVX
1171 << ", surfVY = " << surfVY
1172 << ", surfVZ = " << surfVZ
1173 << ", up = " << up
1174 << ", vp = " << vp
1175 << ", wp = " << wp
1176 << ", rho = " << rho
1177 << ", pp = " << pp
1178 << ", un = " << un
1179 << ", vn = " << vn
1180 << ", deltaUn = " << deltaUn
1181 << ", srcUn = " << std::setprecision(16) << m_surfaceData.variables(segmentId, FWH_TIME::srcUn, tau)
1182 << ", srcLX = " << std::setprecision(16) << m_surfaceData.variables(segmentId, FWH_TIME::srcLX, tau)
1183 << ", srcLY = " << std::setprecision(16) << m_surfaceData.variables(segmentId, FWH_TIME::srcLY, tau)
1184 << ", srcLZ = " << std::setprecision(16) << m_surfaceData.variables(segmentId, FWH_TIME::srcLZ, tau)
1185 << std::endl;
1186 }*/
1187 });
1188 } else if constexpr(nDim == 2) {
1189 TERMM(1, "Time Domain FW-H not implimented in 2D: Requires free space Green function in 2D");
1190 } // End of dimension switch
1191}
1192
1194template <MInt nDim>
1196 const MFloat N = static_cast<MFloat>(noSamples());
1197 MFloat normFactor = 1.0;
1198
1199 switch(m_windowType) {
1200 case WINDOW::NONE: {
1201 m_log << " - using no window function" << std::endl;
1202 break;
1203 }
1204 case WINDOW::HANNING: {
1205 m_log << " - using Hanning window function" << std::endl;
1206 MFloat sum_w_sq = 0.0;
1207 for(MInt t = 0; t < noSamples(); t++) {
1208 const MFloat w = 0.5 * (1.0 - cos(2.0 * PI * t / N));
1209 sum_w_sq += w * w;
1210 p_window[t] = w;
1211 }
1212 normFactor = 1.0 / sqrt(sum_w_sq / N);
1213 break;
1214 }
1215 case WINDOW::HAMMING: {
1216 m_log << " - using Hamming window function" << std::endl;
1217 MFloat sum_w_sq = 0.0;
1218 for(MInt t = 0; t < noSamples(); t++) {
1219 const MFloat w = 0.54 - 0.46 * cos(2.0 * PI * t / N);
1220 sum_w_sq += w * w;
1221 p_window[t] = w;
1222 }
1223 normFactor = 1.0 / sqrt(sum_w_sq / N);
1224 break;
1225 }
1226 case WINDOW::MODHANNING: {
1227 m_log << " - using modified-Hanning window function" << std::endl;
1228 MFloat sum_w_sq = 0.0;
1229 for(MInt t = 0; t < noSamples(); t++) {
1230 MFloat w = 0.0;
1231 if(t < N / 8.0 || t > 7.0 * N / 8.0) {
1232 w = 0.5 * (1.0 - cos(8.0 * PI * t / N));
1233 } else {
1234 w = 1.0;
1235 }
1236 sum_w_sq += w * w;
1237 p_window[t] = w;
1238 }
1239 normFactor = 1.0 / sqrt(sum_w_sq / N);
1240 break;
1241 }
1242 default: {
1243 TERMM(1, "Invalid window type: '" + std::to_string(m_windowType) + "'");
1244 break;
1245 }
1246 }
1247 m_log << " - normalization factor: " << normFactor << std::endl;
1248 m_windowNormFactor = normFactor;
1249}
1250
1252template <MInt nDim>
1254 TRACE();
1255 RECORD_TIMER_START(m_timers[Timers::WindowAndTransformSources]);
1256 printMessage("- Window and transform source terms");
1257
1258 // Compute the window function factors and the normalization factor
1259 std::vector<MFloat> window(noSamples());
1260 std::fill(window.begin(), window.end(), 1.0);
1261 calcWindowFactors(window.data());
1262
1263 // Set source term indices depending on used method
1264 std::vector<MInt> VarDim;
1265 std::vector<MInt> VarDimC;
1266 if(m_acousticMethod == FWH_METHOD) {
1267 if constexpr(nDim == 3) {
1268 VarDim = {FWH::FX, FWH::FY, FWH::FZ, FWH::Q};
1269 VarDimC = {FWH::FX_C, FWH::FY_C, FWH::FZ_C, FWH::Q_C};
1270 } else {
1271 VarDim = {FWH::FX, FWH::FY, FWH::Q};
1272 VarDimC = {FWH::FX_C, FWH::FY_C, FWH::Q_C};
1273 }
1274 } else if(m_acousticMethod == FWH_APE_METHOD) {
1275 if constexpr(nDim == 3) {
1276 VarDim = {FWH_APE::FX, FWH_APE::FY, FWH_APE::FZ, FWH_APE::Q};
1277 VarDimC = {FWH_APE::FX_C, FWH_APE::FY_C, FWH_APE::FZ_C, FWH_APE::Q_C};
1278 } else {
1279 VarDim = {FWH_APE::FX, FWH_APE::FY, FWH_APE::Q};
1280 VarDimC = {FWH_APE::FX_C, FWH_APE::FY_C, FWH_APE::Q_C};
1281 }
1282 } else {
1283 TERMM(1, "Unknown acoustic method.");
1284 }
1285
1286 MInt noSourceTerms = 0;
1287 if constexpr(nDim == 3) {
1288 noSourceTerms = 4;
1289 } else if constexpr(nDim == 2) {
1290 noSourceTerms = 3;
1291 }
1292
1293 const MFloat noSamplesF = static_cast<MFloat>(noSamples());
1294 for(MInt segmentId = 0; segmentId < totalNoSurfaceElements(); segmentId++) {
1295 for(MInt i = 0; i < noSourceTerms; i++) {
1296 // Sum up source term over all samples
1297 MFloat sumSource = 0.0;
1298 for(MInt t = 0; t < noSamples(); t++) {
1299 sumSource += m_surfaceData.variables(segmentId, VarDim[i], t);
1300 }
1301 const MFloat avgSource = sumSource / noSamplesF; // Compute average
1302
1303 // Substract average from all samples to get the fluctuations
1304 // After substracting the mean apply the window function [see e.g. Mendez et al. or Lockard]
1305 for(MInt t = 0; t < noSamples(); t++) {
1306 m_surfaceData.variables(segmentId, VarDim[i], t) -= avgSource;
1307 m_surfaceData.variables(segmentId, VarDim[i], t) *= window[t];
1308 }
1309 }
1310
1311 // Copy variables into complex variables array: Re1, Im1, Re2, Im2, Re3, Im3, ... with Im_n = 0
1312 for(MInt i = 0; i < noSourceTerms; i++) {
1313 for(MInt t = 0; t < noSamples(); t++) {
1314 m_surfaceData.complexVariables(segmentId, VarDimC[i], t, 0) =
1315 m_surfaceData.variables(segmentId, VarDim[i], t); // Real part
1316 m_surfaceData.complexVariables(segmentId, VarDimC[i], t, 1) = 0.0; // Imaginary part
1317 }
1318 }
1319
1320 // Use FFT or DFT to transform source terms (depending on possibility and the set property)
1321 checkNoSamplesPotencyOfTwo();
1322 if(m_FastFourier == true && m_transformationType == 1) {
1323 for(MInt i = 0; i < noSourceTerms; i++) {
1324 FastFourierTransform(&m_surfaceData.complexVariables(segmentId, VarDimC[i]), noSamples(), 1, nullptr, true);
1325 }
1326 } else if(m_FastFourier == false || (m_FastFourier == true && m_transformationType == 0)) {
1327 for(MInt i = 0; i < noSourceTerms; i++) {
1328 // 1 in function means forward; -1 is backward
1329 DiscreteFourierTransform(&m_surfaceData.complexVariables(segmentId, VarDimC[i]), noSamples(), 1, nullptr, true);
1330 }
1331 } else {
1332 TERMM(1, "Error Fourier-Transform: Check transformationType and noSamples.");
1333 }
1334
1335 // Normalize for conservation of energy
1336 for(MInt t = 0; t < noSamples(); t++) {
1337 for(MInt i = 0; i < noSourceTerms; i++) {
1338 m_surfaceData.complexVariables(segmentId, VarDimC[i], t, 0) *= m_windowNormFactor;
1339 m_surfaceData.complexVariables(segmentId, VarDimC[i], t, 1) *= m_windowNormFactor;
1340 }
1341 }
1342
1343 // Calculate frequencies is moved out of segmentId Loop because it is the same for every
1344 // segmentId. Moved to calcSurfaceIntegral Calculate time: It is calculated at
1345 // "generateSurfaceData" m_times = dt * noSamples() Initialize observerdata array. Function
1346 // initObserverPoints
1347 } // End of loop over all segments
1348
1349 RECORD_TIMER_STOP(m_timers[Timers::WindowAndTransformSources]);
1350}
1351
1353template <MInt nDim>
1355 TRACE();
1356
1357 // Compute the window function factors and the normalization factor
1358 std::vector<MFloat> window(noSamples());
1359 std::fill(window.begin(), window.end(), 1.0);
1360 calcWindowFactors(window.data());
1361
1362 const MFloat noSamplesF = static_cast<MFloat>(noSamples());
1363 for(MInt observerId = 0; observerId < noObservers(); observerId++) {
1364 // Sum up observer pressure over all samples
1365 MFloat sumObsPressure = 0.0;
1366 for(MInt t = 0; t < noSamples(); t++) {
1367 sumObsPressure += m_observerData.variables(observerId, 0, t);
1368 }
1369 const MFloat aveObsPressure = sumObsPressure / noSamplesF; // Compute average
1370
1371 // Substract average from all samples to get the fluctuations
1372 // After substracting the mean apply the window function [see e.g. Mendez et al. or Lockard]
1373 // copy variables into complex variables array: re1, im1, re2, im2, re3, im3, ... with im_n = 0
1374 for(MInt t = 0; t < noSamples(); t++) {
1375 // Do nothing to "m_observerData.variables" output the original data
1376 m_observerData.complexVariables(observerId, 0, t, 0) =
1377 window[t] * (m_observerData.variables(observerId, 0, t) - aveObsPressure); // Real part
1378 m_observerData.complexVariables(observerId, 0, t, 1) = 0.0; // Imaginary part
1379 }
1380
1381 // Use FFT or DFT to transform source terms (depending on possibility and the set property)
1382 checkNoSamplesPotencyOfTwo();
1383 if(m_FastFourier == true && m_transformationType == 1) {
1384 FastFourierTransform(&m_observerData.complexVariables(observerId, 0), noSamples(), 1, nullptr, true);
1385 } else if(m_FastFourier == false || (m_FastFourier == true && m_transformationType == 0)) {
1386 // 1 in function means forward; -1 is backward
1387 DiscreteFourierTransform(&m_observerData.complexVariables(observerId, 0), noSamples(), 1, nullptr, true);
1388 } else {
1389 TERMM(1, "Error Fourier-Transform: Check transformationType and noSamples.");
1390 }
1391
1392 // Normalize for conservation of energy
1393 for(MInt t = 0; t < noSamples(); t++) {
1394 m_observerData.complexVariables(observerId, 0, t, 0) *= m_windowNormFactor;
1395 m_observerData.complexVariables(observerId, 0, t, 1) *= m_windowNormFactor;
1396 }
1397 } // End of observer loop
1398}
1399
1400
1410template <MInt nDim>
1411void AcaSolver<nDim>::FastFourierTransform(MFloat* dataArray, const MInt size, const MInt dir, MFloat* result,
1412 const MBool inPlace) {
1413 TRACE();
1414 TERMM_IF_NOT_COND(dir == 1 || dir == -1, "Invalid direction");
1415 TERMM_IF_NOT_COND(size % 2 == 0, "FFT: not a multiple of 2 samples.");
1416
1417 const MFloat isign = (dir == 1) ? -1.0 : 1.0;
1418 const MBool dim = (dir == 1) ? true : false;
1419 const MInt size2 = 2 * size; // full array size (real+imag)
1420
1421 // Set pointer to result storage
1422 MFloat* const data = (inPlace) ? dataArray : result;
1423 // Copy input data if not performing transform in-place (overwriting input)
1424 if(!inPlace) {
1425 ASSERT(result != nullptr, "Pointer to result storage is nullptr.");
1426 std::copy_n(&dataArray[0], size2, result);
1427 }
1428
1429#define SWAP(a, b) \
1430 rtemp = (a); \
1431 (a) = (b); \
1432 (b) = rtemp
1433 // Bit reversing: Reorder by inverse bit notation, e.g. first number: 0000 --> 0000, second
1434 // number: 0001 -> 1000
1435 const MInt nn = size;
1436 MInt n = nn << 1;
1437 MInt m;
1438 MFloat rtemp;
1439 MInt j = 1;
1440 for(MInt i = 1; i < n; i += 2) {
1441 if(j > i) {
1442 SWAP(data[j - 1], data[i - 1]);
1443 SWAP(data[j], data[i]);
1444 }
1445 m = nn;
1446 while(m >= 2 && j > m) {
1447 j -= m;
1448 m >>= 1;
1449 }
1450 j += m;
1451 }
1452
1453 // Fourier Transformation (Danielson-Lanczos)
1454 MUlong istep, p, k, i;
1455 MFloat wtemp, wr, wpr, wpi, wi, theta, tempr, tempi;
1456 const MUlong nLong = static_cast<MUlong>(size2);
1457 MUlong mmax = 2;
1458 while(nLong > mmax) {
1459 istep = mmax << 1;
1460 theta = isign * (6.28318530717959 / mmax);
1461 wtemp = sin(0.5 * theta);
1462 wpr = -2.0 * wtemp * wtemp;
1463 wpi = sin(theta);
1464 wr = 1.0;
1465 wi = 0.0;
1466 for(p = 1; p < mmax; p += 2) {
1467 for(i = p; i <= nLong; i += istep) {
1468 k = i + mmax;
1469 tempr = wr * data[k - 1] - wi * data[k];
1470 tempi = wr * data[k] + wi * data[k - 1];
1471 data[k - 1] = data[i - 1] - tempr;
1472 data[k] = data[i] - tempi;
1473 data[i - 1] += tempr;
1474 data[i] += tempi;
1475 }
1476 wr = (wtemp = wr) * wpr - wi * wpi + wr;
1477 wi = wi * wpr + wtemp * wpi + wi;
1478 }
1479 mmax = istep;
1480 }
1481
1482 // Dimensionierung
1483 if(dim) {
1484 for(MInt z = 0; z < size2; z++) {
1485 data[z] /= size;
1486 }
1487 }
1488}
1489
1490
1499template <MInt nDim>
1500void AcaSolver<nDim>::DiscreteFourierTransform(MFloat* dataArray, const MInt size, const MInt dir, MFloat* result,
1501 const MBool inPlace) {
1502 TRACE();
1503 const MFloat sizeF = static_cast<MFloat>(size);
1504 const MInt size2 = 2 * size;
1505 MFloat* resultPtr = nullptr;
1506 std::vector<MFloat> resultArray;
1507
1508 if(inPlace) {
1509 resultArray.resize(size2); // Temporary solution storage
1510 resultPtr = &resultArray[0];
1511 } else {
1512 ASSERT(result != nullptr, "Pointer to result storage is nullptr.");
1513 resultPtr = result; // Store solution in provided storage
1514 }
1515
1516 if(dir == 1) { // FOURIER FORWARD
1517 const MFloat con = 2.0 * PI / sizeF;
1518 for(MInt k = 0; k < size; k++) {
1519 for(MInt i = 0; i < size; i++) {
1520 // cos(-a) = cos(a) and sin(-a) = -sin(a)
1521 // Actual Discrete Fourier Transform
1522 resultPtr[2 * k] += dataArray[2 * i] * cos(k * con * i) + dataArray[2 * i + 1] * sin(k * con * i);
1523 resultPtr[2 * k + 1] += -1.0 * dataArray[2 * i] * sin(k * con * i) + dataArray[2 * i + 1] * cos(k * con * i);
1524 }
1525 resultPtr[2 * k] /= sizeF;
1526 resultPtr[2 * k + 1] /= sizeF;
1527 }
1528 } else if(dir == -1) { // FOURIER BACKWARD
1529 for(MInt t = 0; t < size; t++) {
1530 const MFloat arg = 2.0 * PI * t / sizeF;
1531 for(MInt k = 0; k < size; k++) {
1532 resultPtr[2 * t] += dataArray[2 * k] * cos(arg * k) - dataArray[2 * k + 1] * sin(arg * k);
1533 resultPtr[2 * t + 1] += dataArray[2 * k] * sin(arg * k) + dataArray[2 * k + 1] * cos(arg * k);
1534 }
1535 }
1536 } else {
1537 TERMM(1, "Please insert 1 for forward or -1 for backward");
1538 }
1539
1540 if(inPlace) {
1541 // Copy results back into dataArray overwriting the input data
1542 std::copy_n(&resultPtr[0], size2, &dataArray[0]);
1543 }
1544}
1545
1546
1548template <MInt nDim>
1550 TRACE();
1551 printMessage("- Calculate frequencies");
1552 // Calculate frequencies, m_frequencies is already resized with noSamples
1553 // Numerical Recipes in C by Cambridge page 503
1554 // Structure: 0 +1 +2 +3 ... +noSamples/2 ... -3 -2 -1
1555 for(MInt i = 0, k = noSamples() - 1; i <= noSamples() / 2; k--, i++) {
1556 if(k > noSamples() / 2) {
1557 m_frequencies[k] = -1.0 * static_cast<MFloat>(i + 1) / (noSamples() * m_dt);
1558 }
1559 m_frequencies[i] = static_cast<MFloat>(i) / (noSamples() * m_dt);
1560 }
1561 // Frequencies from Hz --> rad/s: now omega
1562 for(MInt i = 0; i < noSamples(); i++) {
1563 m_frequencies[i] *= 2.0 * PI;
1564 }
1565}
1566
1572template <MInt nDim>
1573void AcaSolver<nDim>::calcSurfaceIntegralsForObserver(const std::array<MFloat, nDim> coord,
1574 MFloat* const p_complexVariables) {
1575 RECORD_TIMER_START(m_timers[Timers::CalcSurfaceIntegrals]);
1576 std::vector<MInt> VarDimC;
1577 if(m_acousticMethod == FWH_METHOD) {
1578 if constexpr(nDim == 3) {
1579 VarDimC = {FWH::FX_C, FWH::FY_C, FWH::FZ_C, FWH::Q_C};
1580 } else {
1581 VarDimC = {FWH::FX_C, FWH::FY_C, FWH::Q_C};
1582 }
1583 } else if(m_acousticMethod == FWH_APE_METHOD) {
1584 if constexpr(nDim == 3) {
1585 VarDimC = {FWH_APE::FX_C, FWH_APE::FY_C, FWH_APE::FZ_C, FWH_APE::Q_C};
1586 } else {
1587 VarDimC = {FWH_APE::FX_C, FWH_APE::FY_C, FWH_APE::Q_C};
1588 }
1589 } else {
1590 TERMM(1, "Unknown acoustic method.");
1591 }
1592
1593 // Calculate Mach number
1594 const MFloat mach = sqrt(std::inner_product(&m_MaVec[0], &m_MaVec[nDim], &m_MaVec[0], 0.0));
1595 // Prandtl-Glauert-Factor
1596 const MFloat betaSq = 1.0 - mach * mach;
1597 const MFloat beta = sqrt(betaSq);
1598 const MFloat fbeta2 = 1 / betaSq;
1599
1600 if constexpr(nDim == 2) {
1601 const MFloat Max = m_MaVec[0];
1602 const MFloat May = m_MaVec[1];
1603
1604 MFloat theta = 0;
1605 if(!approx(Max, 0.0, MFloatEps) && !approx(May, 0.0, MFloatEps)) {
1606 theta = atan2(May, Max);
1607 }
1608
1609 // Pre-computing sine and cosine terms
1610 const MFloat sinTheta = sin(theta);
1611 const MFloat cosTheta = cos(theta);
1612
1613 // INNER LOOP OVER ALL FREQUENCIES (with only positive frequencies because bessel is not valid
1614 // for negativ nor zero). Since nw = 0 is always a 0, it is left out in the loop --> start at
1615 // nw = 1
1616 maia::parallelFor<false>(1, noSamples() / 2 + 1, [=](MInt nw) {
1617 // non-dimensionalized omega is already in m_frequencies
1618 const MFloat waveNumber = m_frequencies[nw];
1619
1620 // Set everything to 0
1621 MFloat integralFxRe = 0.0;
1622 MFloat integralFxIm = 0.0;
1623 MFloat integralFyRe = 0.0;
1624 MFloat integralFyIm = 0.0;
1625 MFloat integralQRe = 0.0;
1626 MFloat integralQIm = 0.0;
1627
1628 // LOOP OVER ALL LOCAL SURFACE ELEMENTS
1629 for(MInt segmentId = 0; segmentId < totalNoSurfaceElements(); segmentId++) {
1630 // Calculation of Prandtl-Glauert-Coordinates
1631 // Division by reference length is leaved out bc it would cancel out later
1632 const MFloat* const surfCoordinates = &m_surfaceData.surfaceCoordinates(segmentId);
1633 const MFloat surfX = surfCoordinates[0];
1634 const MFloat surfY = surfCoordinates[1];
1635 const MFloat X = (coord[0] - surfX) * cosTheta + (coord[1] - surfY) * sinTheta;
1636 const MFloat Y = -1.0 * (coord[0] - surfX) * sinTheta + (coord[1] - surfY) * cosTheta;
1637
1638 const MFloat d = sqrt(X * X + betaSq * Y * Y);
1639 const MFloat fd = 1 / d;
1640
1641 // Argument in Hankelfunction argH and its derivatives
1642 const MFloat argH = waveNumber * fbeta2 * d;
1643 const MFloat dargHdxi = waveNumber * fbeta2 * fd * (-1.0 * X * cosTheta + betaSq * Y * sinTheta);
1644 const MFloat dargHdeta = waveNumber * fbeta2 * fd * (-1.0 * X * sinTheta - betaSq * Y * cosTheta);
1645
1646 // Argument for exp function and its derivaties
1647 const MFloat argExp = mach * waveNumber * X * fbeta2;
1648 const MFloat dargEdxi = -1.0 * mach * waveNumber * cosTheta * fbeta2;
1649 const MFloat dargEdeta = -1.0 * mach * waveNumber * sinTheta * fbeta2;
1650 const MFloat C1 = 1.0 / (4.0 * beta);
1651
1652 const MFloat J0 = j0(argH);
1653 const MFloat J1 = j1(argH);
1654 const MFloat Y0 = y0(argH);
1655 const MFloat Y1 = y1(argH);
1656
1657 const MFloat cosargE = cos(argExp);
1658 const MFloat sinargE = sin(argExp);
1659
1660 // Calculation of the Green's function. y0, j0, y1 and j1 are used for Hankel function
1661 const MFloat greenValue_Re = C1 * (cosargE * Y0 - sinargE * J0);
1662 const MFloat greenValue_Im = C1 * (cosargE * J0 + sinargE * Y0);
1663
1664 // Calculation of the derivation of the Greenfunction resp. xi and eta
1665 const MFloat dGreendXi_Re =
1666 C1 * (dargEdxi * (-1.0 * cosargE * J0 - sinargE * Y0) + dargHdxi * (-1.0 * cosargE * Y1 + sinargE * J1));
1667 const MFloat dGreendXi_Im =
1668 C1 * (dargEdxi * (cosargE * Y0 - sinargE * J0) + dargHdxi * (-1.0 * cosargE * J1 - sinargE * Y1));
1669 const MFloat dGreendEta_Re =
1670 C1 * (dargEdeta * (-1.0 * cosargE * J0 - sinargE * Y0) + dargHdeta * (-1.0 * cosargE * Y1 + sinargE * J1));
1671 const MFloat dGreendEta_Im =
1672 C1 * (dargEdeta * (cosargE * Y0 - sinargE * J0) + dargHdeta * (-1.0 * cosargE * J1 - sinargE * Y1));
1673
1674 // Get Grid size dL
1675 const MFloat dL = m_surfaceData.surfaceArea(segmentId);
1676 // Get local values
1677 const MFloat FxRe = m_surfaceData.complexVariables(segmentId, VarDimC[0], nw, 0);
1678 const MFloat FxIm = m_surfaceData.complexVariables(segmentId, VarDimC[0], nw, 1);
1679
1680 const MFloat FyRe = m_surfaceData.complexVariables(segmentId, VarDimC[1], nw, 0);
1681 const MFloat FyIm = m_surfaceData.complexVariables(segmentId, VarDimC[1], nw, 1);
1682
1683 const MFloat QRe = m_surfaceData.complexVariables(segmentId, VarDimC[2], nw, 0);
1684 const MFloat QIm = m_surfaceData.complexVariables(segmentId, VarDimC[2], nw, 1);
1685
1686 // Calculate integral terms
1687 integralFxRe += dL * (FxRe * dGreendXi_Re - FxIm * dGreendXi_Im);
1688 integralFxIm += dL * (FxRe * dGreendXi_Im + FxIm * dGreendXi_Re);
1689
1690 integralFyRe += dL * (FyRe * dGreendEta_Re - FyIm * dGreendEta_Im);
1691 integralFyIm += dL * (FyRe * dGreendEta_Im + FyIm * dGreendEta_Re);
1692
1693 integralQRe += -1.0 * waveNumber * dL * (QRe * greenValue_Im + QIm * greenValue_Re);
1694 integralQIm += waveNumber * dL * (QRe * greenValue_Re - QIm * greenValue_Im);
1695 } // end segmentId Loop
1696
1697 // sum up all integral terms
1698 const MFloat integralRe = -1.0 * (integralFxRe + integralFyRe + integralQRe);
1699 const MFloat integralIm = -1.0 * (integralFxIm + integralFyIm + integralQIm);
1700
1701 // Store pressure values in observerData
1702 p_complexVariables[2 * nw + 0] = integralRe;
1703 p_complexVariables[2 * nw + 1] = integralIm;
1704 });
1705 } else if constexpr(nDim == 3) {
1706 const MFloat Max = m_MaVec[0];
1707 const MFloat May = m_MaVec[1];
1708 const MFloat Maz = m_MaVec[2];
1709
1710 MFloat theta = 0.0;
1711 MFloat alpha = 0.0;
1712 // Determination of the flow direction
1713 // Use approx(..., 0.0, MFloatEps) instead of Maz == 0.0 || May == 0.0. Compares Ma with a
1714 // really low number epsilon
1715 if(!approx(Maz, 0.0, MFloatEps) && !approx(Max, 0.0, MFloatEps)) {
1716 theta = atan2(Maz, Max);
1717 }
1718 if(!approx(May, 0.0, MFloatEps)) {
1719 alpha = asin(May / mach);
1720 }
1721
1722 // Precompute trigonometric values
1723 const MFloat sinAlpha = sin(alpha);
1724 const MFloat cosAlpha = cos(alpha);
1725 const MFloat sinTheta = sin(theta);
1726 const MFloat cosTheta = cos(theta);
1727
1728 // FREQUENCIE LOOP: only for positive frequencies without nw=0
1729 maia::parallelFor<false>(1, noSamples() / 2 + 1, [=](MInt nw) {
1730 // Calculate wave number: Non-dimensionalized omega is already saved in m_frequencies
1731 const MFloat waveNumber = m_frequencies[nw];
1732
1733 // Set values to zero
1734 MFloat integralFxRe = 0.0;
1735 MFloat integralFxIm = 0.0;
1736 MFloat integralFyRe = 0.0;
1737 MFloat integralFyIm = 0.0;
1738 MFloat integralFzRe = 0.0;
1739 MFloat integralFzIm = 0.0;
1740 MFloat integralQRe = 0.0;
1741 MFloat integralQIm = 0.0;
1742
1743 // SURFACE LOOP
1744 for(MInt segmentId = 0; segmentId < totalNoSurfaceElements(); segmentId++) {
1745 MFloat* surfCoordinates = &m_surfaceData.surfaceCoordinates(segmentId);
1746
1747 const MFloat x = coord[0];
1748 const MFloat y = coord[1];
1749 const MFloat z = coord[2];
1750 const MFloat xi = surfCoordinates[0];
1751 const MFloat eta = surfCoordinates[1];
1752 const MFloat zeta = surfCoordinates[2];
1753
1754 /*
1755 // Version without Prandtl-Glauert
1756 const MFloat X = x - xi;
1757 const MFloat Y = y - eta;
1758 const MFloat Z = z - zeta;
1759 const MFloat R = sqrt(POW2(X) + POW2(Y) + POW2(Z));
1760
1761 const MFloat arg = waveNumber * R;
1762 const MFloat sinarg = sin(arg);
1763 const MFloat cosarg = cos(arg);
1764 const MFloat factor1 = waveNumber / (4.0 * PI * POW2(R));
1765 const MFloat factor2 = 1.0 / (4.0 * PI * POW3(R));
1766 const MFloat factorRe = -factor1 * sinarg - factor2 * cosarg;
1767 const MFloat factorIm = -factor1 * cosarg + factor2 * sinarg;
1768
1769 const MFloat greenValue_Re = -cosarg / (4.0 * PI * R);
1770 const MFloat greenValue_Im = sinarg / (4.0 * PI * R);
1771 const MFloat dGreendXi_Re = X * factorRe;
1772 const MFloat dGreendXi_Im = X * factorIm;
1773 const MFloat dGreendEta_Re = Y * factorRe;
1774 const MFloat dGreendEta_Im = Y * factorIm;
1775 const MFloat dGreendZeta_Re = Z * factorRe;
1776 const MFloat dGreendZeta_Im = Z * factorIm;
1777 */
1778
1779 // Version with Prandtl-Glauert, cf. Lockard2002
1780 const MFloat X = (x - xi) * cosAlpha * cosTheta + (y - eta) * sinAlpha + (z - zeta) * cosAlpha * sinTheta;
1781 // The Y coordinate differs from the paper, as there is a typo of the sign in front of the (z-zeta) term
1782 const MFloat Y = -(x - xi) * sinAlpha * cosTheta + (y - eta) * cosAlpha - (z - zeta) * sinAlpha * sinTheta;
1783 const MFloat Z = -(x - xi) * sinTheta + (z - zeta) * cosTheta;
1784 const MFloat d = std::sqrt(X * X + betaSq * (Y * Y + Z * Z));
1785 const MFloat k = waveNumber;
1786
1787 // partial d / partial xi
1788 const MFloat F1B4pid = 1.0 / (4.0 * PI * d);
1789 const MFloat d_xi = (-X * cosAlpha * cosTheta + betaSq * (Y * sinAlpha * cosTheta + Z * sinTheta)) / d;
1790 const MFloat d_eta = (-X * sinAlpha - betaSq * Y * cosAlpha) / d;
1791 const MFloat d_zeta = (-X * cosAlpha * sinTheta + betaSq * (-Y * sinAlpha * sinTheta - Z * cosTheta)) / d;
1792
1793 const MFloat arg = k * (mach * X - d) / betaSq;
1794 const MFloat cosArg = cos(arg);
1795 const MFloat sinArg = sin(arg);
1796
1797 // Pre-terms in the derivatives
1798 const MFloat P0 = F1B4pid * mach * k / betaSq;
1799 const MFloat P1 = F1B4pid / d;
1800 const MFloat P2 = F1B4pid * k / betaSq;
1801
1802 // clang-format off
1803 const MFloat greenValue_Re = - F1B4pid * cosArg;
1804 const MFloat greenValue_Im = - F1B4pid * sinArg;
1805 const MFloat dGreendXi_Re =-P0 * sinArg * cosAlpha * cosTheta + P1 * d_xi * cosArg - P2 * d_xi * sinArg;
1806 const MFloat dGreendXi_Im = P0 * cosArg * cosAlpha * cosTheta + P1 * d_xi * sinArg + P2 * d_xi * cosArg;
1807 const MFloat dGreendEta_Re =-P0 * sinArg * sinAlpha + P1 * d_eta * cosArg - P2 * d_eta * sinArg;
1808 const MFloat dGreendEta_Im = P0 * cosArg * sinAlpha + P1 * d_eta * sinArg + P2 * d_eta * cosArg;
1809 const MFloat dGreendZeta_Re =-P0 * sinArg * cosAlpha * sinTheta + P1 * d_zeta * cosArg - P2 * d_zeta * sinArg;
1810 const MFloat dGreendZeta_Im = P0 * cosArg * cosAlpha * sinTheta + P1 * d_zeta * sinArg + P2 * d_zeta * cosArg;
1811 // clang-format on
1812
1813 const MFloat dL = m_surfaceData.surfaceArea(segmentId);
1814
1815 const MFloat FxRe = m_surfaceData.complexVariables(segmentId, VarDimC[0], nw, 0);
1816 const MFloat FxIm = m_surfaceData.complexVariables(segmentId, VarDimC[0], nw, 1);
1817
1818 const MFloat FyRe = m_surfaceData.complexVariables(segmentId, VarDimC[1], nw, 0);
1819 const MFloat FyIm = m_surfaceData.complexVariables(segmentId, VarDimC[1], nw, 1);
1820
1821 const MFloat FzRe = m_surfaceData.complexVariables(segmentId, VarDimC[2], nw, 0);
1822 const MFloat FzIm = m_surfaceData.complexVariables(segmentId, VarDimC[2], nw, 1);
1823
1824 const MFloat QRe = m_surfaceData.complexVariables(segmentId, VarDimC[3], nw, 0);
1825 const MFloat QIm = m_surfaceData.complexVariables(segmentId, VarDimC[3], nw, 1);
1826
1827 // Calculation of integrals
1828 integralFxRe += dL * (FxRe * dGreendXi_Re - FxIm * dGreendXi_Im);
1829 integralFxIm += dL * (FxRe * dGreendXi_Im + FxIm * dGreendXi_Re);
1830
1831 integralFyRe += dL * (FyRe * dGreendEta_Re - FyIm * dGreendEta_Im);
1832 integralFyIm += dL * (FyRe * dGreendEta_Im + FyIm * dGreendEta_Re);
1833
1834 integralFzRe += dL * (FzRe * dGreendZeta_Re - FzIm * dGreendZeta_Im);
1835 integralFzIm += dL * (FzRe * dGreendZeta_Im + FzIm * dGreendZeta_Re);
1836
1837 integralQRe += -1.0 * waveNumber * dL * (QRe * greenValue_Im + QIm * greenValue_Re);
1838 integralQIm += waveNumber * dL * (QRe * greenValue_Re - QIm * greenValue_Im);
1839 } // End of segmentId loop
1840
1841 // sum up all integral terms
1842 const MFloat integralRe = -1.0 * (integralFxRe + integralFyRe + integralFzRe + integralQRe);
1843 const MFloat integralIm = -1.0 * (integralFxIm + integralFyIm + integralFzIm + integralQIm);
1844
1845 // Saving Pressure in Observerdata variable p for each positive frequencie nw between 1 and
1846 // size/2+1. 0 = RE; 1 = IM
1847 p_complexVariables[2 * nw + 0] = integralRe;
1848 p_complexVariables[2 * nw + 1] = integralIm;
1849 });
1850 }
1851 // nw = 0 is not defined and therefore set to zero to avoid "NaN-errors"
1852 const MInt nw = 0;
1853 p_complexVariables[2 * nw + 0] = 0.0;
1854 p_complexVariables[2 * nw + 1] = 0.0;
1855 RECORD_TIMER_STOP(m_timers[Timers::CalcSurfaceIntegrals]);
1856}
1857
1865template <MInt nDim>
1867 // Get observer coordinates
1868 const std::array<MFloat, nDim> coord = getGlobalObserverCoordinates(globalObserverId);
1869
1870 // Calculate Mach number and the Prandtl-Glauert-Factor
1871 const MFloat mach = sqrt(std::inner_product(&m_MaVec[0], &m_MaVec[nDim], &m_MaVec[0], 0.0));
1872 const MFloat betaSq = 1.0 - mach * mach;
1873
1874 // Loop over all the source timeSteps
1875 maia::parallelFor<false>(0, noSamples(), [=](MInt tau) {
1876 if constexpr(nDim == 3) {
1877 // SURFACE LOOP
1878 for(MInt segmentId = 0; segmentId < totalNoSurfaceElements(); segmentId++) {
1879 MFloat* surfCoordinates = &m_surfaceData.surfaceCoordinates(segmentId);
1880
1881 // Geometric Distance & Coordinate Transformation
1882 MFloat distX = coord[0] - surfCoordinates[0];
1883 MFloat distY = coord[1] - surfCoordinates[1];
1884 MFloat distZ = coord[2] - surfCoordinates[2];
1885 transformCoordinatesToFlowDirection3D(&distX, &distY, &distZ);
1886
1887 // Effective Acoustic Distance
1888 // Reference: Brès, G., 2010, A Ffowcs Williams - Hawkings ... doi.org/10.2514/6.2010-3711
1889 const MFloat R = sqrt(distX * distX + betaSq * (distY * distY + distZ * distZ));
1890 const MFloat dist = (R - mach * distX) / betaSq;
1891
1892 const MFloat invsersDist = 1.0 / dist;
1893 const MFloat invsersDistSq = invsersDist * invsersDist;
1894 const MFloat distUnitX = (distX - mach * R) * invsersDist / betaSq;
1895 const MFloat distUnitY = distY * invsersDist;
1896 const MFloat distUnitZ = distZ * invsersDist;
1897
1898 // compute observer time
1899 const MFloat timeSource = tau * m_dt;
1900 const MFloat timeObserver = timeSource + dist;
1901
1902 // Get surface Mach number and source terms
1903 const MFloat Mx = m_surfaceData.variables(segmentId, FWH_TIME::surfMX, tau);
1904 const MFloat My = m_surfaceData.variables(segmentId, FWH_TIME::surfMY, tau);
1905 const MFloat Mz = m_surfaceData.variables(segmentId, FWH_TIME::surfMZ, tau);
1906 const MFloat srcUn = m_surfaceData.variables(segmentId, FWH_TIME::srcUn, tau);
1907 const MFloat srcLX = m_surfaceData.variables(segmentId, FWH_TIME::srcLX, tau);
1908 const MFloat srcLY = m_surfaceData.variables(segmentId, FWH_TIME::srcLY, tau);
1909 const MFloat srcLZ = m_surfaceData.variables(segmentId, FWH_TIME::srcLZ, tau);
1910
1911 // Project variables on the radiation direction
1912 const MFloat Mr = distUnitX * Mx + distUnitY * My + distUnitZ * Mz;
1913 const MFloat inverseDopplerFactor = 1.0 / (1.0 - Mr);
1914 const MFloat inverseDopplerFactorSq = inverseDopplerFactor * inverseDopplerFactor;
1915 const MFloat Lr = distUnitX * srcLX + distUnitY * srcLY + distUnitZ * srcLZ;
1916 const MFloat LM = srcLX * Mx + srcLY * My + srcLZ * Mz;
1917
1918 // Compute time derivative in second order accuracy
1919 const MFloat dotMX = calcTimeDerivative(segmentId, FWH_TIME::surfMX, tau);
1920 const MFloat dotMY = calcTimeDerivative(segmentId, FWH_TIME::surfMY, tau);
1921 const MFloat dotMZ = calcTimeDerivative(segmentId, FWH_TIME::surfMZ, tau);
1922 const MFloat dotUn = calcTimeDerivative(segmentId, FWH_TIME::srcUn, tau);
1923 const MFloat dotLX = calcTimeDerivative(segmentId, FWH_TIME::srcLX, tau);
1924 const MFloat dotLY = calcTimeDerivative(segmentId, FWH_TIME::srcLY, tau);
1925 const MFloat dotLZ = calcTimeDerivative(segmentId, FWH_TIME::srcLZ, tau);
1926
1927 // Project time derivative on the radiation direction
1928 const MFloat dotMr = distUnitX * dotMX + distUnitY * dotMY + distUnitZ * dotMZ;
1929 const MFloat dotLr = distUnitX * dotLX + distUnitY * dotLY + distUnitZ * dotLZ;
1930
1931 const MFloat MrFactor0 = invsersDist * inverseDopplerFactorSq;
1932 const MFloat MrFactor1 =
1933 (dist * dotMr + Mr - mach * mach) * invsersDistSq * inverseDopplerFactor * inverseDopplerFactorSq;
1934
1935 // Compute Thickness Source
1936 const MFloat surfIntegralPPT_0 = dotUn * MrFactor0;
1937 const MFloat surfIntegralPPT_1 = srcUn * MrFactor1;
1938
1939 // Compute Loading Source
1940 const MFloat surfIntegralPPL_0 = dotLr * MrFactor0;
1941 const MFloat surfIntegralPPL_1 = (Lr - LM) * MrFactor0 * invsersDist;
1942 const MFloat surfIntegralPPL_2 = Lr * MrFactor1;
1943
1944 // Save the computed source value
1945 m_surfaceData.variables(segmentId, FWH_TIME::timeObs, tau) = timeObserver;
1946 m_surfaceData.variables(segmentId, FWH_TIME::fwhPP, tau) =
1947 surfIntegralPPT_0 + surfIntegralPPT_1 + surfIntegralPPL_0 + surfIntegralPPL_1 + surfIntegralPPL_2;
1948 m_surfaceData.variables(segmentId, FWH_TIME::fwhPP, tau) *= m_surfaceData.surfaceArea(segmentId);
1949
1950 /*// Debugging
1951 if(segmentId == 0 && tau == 0) {
1952 m_log << "tau = " << tau
1953 << ", timeSource = " << timeSource
1954 << ", timeObserver = " << timeObserver
1955 << ", coord[0] = " << coord[0]
1956 << ", coord[1] = " << coord[1]
1957 << ", coord[2] = " << coord[2]
1958 << ", surfCoordinates[0] = " << surfCoordinates[0]
1959 << ", surfCoordinates[1] = " << surfCoordinates[1]
1960 << ", surfCoordinates[2] = " << surfCoordinates[2]
1961 << ", distX = " << distX
1962 << ", distY = " << distY
1963 << ", distZ = " << distZ
1964 << ", dist = " << dist
1965 << ", distUnitX = " << distUnitX
1966 << ", distUnitY = " << distUnitY
1967 << ", distUnitZ = " << distUnitZ
1968 << ", dL = " << m_surfaceData.surfaceArea(segmentId)
1969 << ", srcUn = " << srcUn
1970 << ", dotUn = " << dotUn
1971 << ", Lr = " << Lr
1972 << ", dotLr = " << dotLr
1973 << ", LM = " << LM
1974 << ", Mr = " << Mr
1975 << ", dotMr = " << dotMr
1976 << ", MSq = " << MSq
1977 << ", MrFactor0 = " << MrFactor0
1978 << ", MrFactor1 = " << MrFactor1
1979 << ", surfIntegralPPT_0= " << surfIntegralPPT_0
1980 << ", surfIntegralPPT_1= " << surfIntegralPPT_1
1981 << ", surfIntegralPPL_0= " << surfIntegralPPL_0
1982 << ", surfIntegralPPL_1= " << surfIntegralPPL_1
1983 << ", surfIntegralPPL_2= " << surfIntegralPPL_2
1984 << std::endl;
1985 }*/
1986 } // End of surface loop
1987 } else if constexpr(nDim == 2) {
1988 TERMM(1, "Time Domain FW-H not implimented in 2D: Requires free space Green function in 2D");
1989 } // End of the dimension switch
1990 }); // End of the time loop
1991}
1992
1993template <MInt nDim>
1995 if(m_zeroFlowAngle) return;
1996
1997 // Project the vector on the flow direction U_inf = (U_inf, 0)
1998 const MFloat fX_old = *fX;
1999 const MFloat fY_old = *fY;
2000
2001 *fX = fX_old * m_transformationMatrix[0] + fY_old * m_transformationMatrix[1];
2002 *fY = fX_old * m_transformationMatrix[2] + fY_old * m_transformationMatrix[3];
2003}
2004
2005template <MInt nDim>
2007 if(m_zeroFlowAngle) return;
2008
2009 // Project the vector on the flow direction U_inf = (U_inf, 0, 0)
2010 const MFloat fX_old = *fX;
2011 const MFloat fY_old = *fY;
2012 const MFloat fZ_old = *fZ;
2013
2014 *fX = fX_old * m_transformationMatrix[0] + fY_old * m_transformationMatrix[1] + fZ_old * m_transformationMatrix[2];
2015 *fY = fX_old * m_transformationMatrix[3] + fY_old * m_transformationMatrix[4] + fZ_old * m_transformationMatrix[5];
2016 *fZ = fX_old * m_transformationMatrix[6] + fY_old * m_transformationMatrix[7] + fZ_old * m_transformationMatrix[8];
2017}
2018
2019template <MInt nDim>
2021 if(m_zeroFlowAngle) return;
2022
2023 // Back transform to the original coordinate system
2024 // Use negative angular position (-theta) in m_transformationMatrix calculation
2025 const MFloat fX_old = *fX;
2026 const MFloat fY_old = *fY;
2027
2028 *fX = fX_old * m_transformationMatrix[0] - fY_old * m_transformationMatrix[1];
2029 *fY = -fX_old * m_transformationMatrix[2] + fY_old * m_transformationMatrix[3];
2030}
2031
2032template <MInt nDim>
2034 if(m_zeroFlowAngle) return;
2035
2036 // Back transform to the original coordinate system
2037 // Use negative angular position (-alpha) and (-theta) in m_transformationMatrix calculation
2038 const MFloat fX_old = *fX;
2039 const MFloat fY_old = *fY;
2040 const MFloat fZ_old = *fZ;
2041
2042 *fX = fX_old * m_transformationMatrix[0] - fY_old * m_transformationMatrix[1] - fZ_old * m_transformationMatrix[2];
2043 *fY = -fX_old * m_transformationMatrix[3] + fY_old * m_transformationMatrix[4] + fZ_old * m_transformationMatrix[5];
2044 *fZ = -fX_old * m_transformationMatrix[6] + fY_old * m_transformationMatrix[7] + fZ_old * m_transformationMatrix[8];
2045}
2046
2047template <MInt nDim>
2048MFloat AcaSolver<nDim>::calcTimeDerivative(const MInt segmentId, const MInt varId, const MInt tau) {
2049 // Compute time derivative in second order accuarate
2050 MFloat dotVar = 0.0;
2051
2052 if(tau == 0) {
2053 dotVar = -1.0 * m_surfaceData.variables(segmentId, varId, tau + 2)
2054 + 4.0 * m_surfaceData.variables(segmentId, varId, tau + 1)
2055 - 3.0 * m_surfaceData.variables(segmentId, varId, tau);
2056 } else if(tau == noSamples() - 1) {
2057 dotVar = +3.0 * m_surfaceData.variables(segmentId, varId, tau)
2058 - 4.0 * m_surfaceData.variables(segmentId, varId, tau - 1)
2059 + 1.0 * m_surfaceData.variables(segmentId, varId, tau - 2);
2060 } else {
2061 dotVar = +1.0 * m_surfaceData.variables(segmentId, varId, tau + 1)
2062 - 1.0 * m_surfaceData.variables(segmentId, varId, tau - 1);
2063 }
2064 return (dotVar / 2.0 / m_dt);
2065}
2066
2068template <MInt nDim>
2070 if(m_fwhTimeShiftType == 0) return;
2071
2072 // Calculate Mach number and the Prandtl-Glauert-Factor
2073 const MFloat mach = sqrt(std::inner_product(&m_MaVec[0], &m_MaVec[nDim], &m_MaVec[0], 0.0));
2074 const MFloat betaSq = 1.0 - mach * mach;
2075
2076 printMessage("- Loop over observers to find minimum distance from the source to the observers");
2077 for(MInt k = 0; k < globalNoObservers(); k++) {
2078 MFloat distMinLocal = std::numeric_limits<MFloat>::max();
2079
2080 // Get observer coordinates
2081 const std::array<MFloat, nDim> coord = getGlobalObserverCoordinates(k);
2082
2083 // SURFACE LOOP
2084 for(MInt segmentId = 0; segmentId < totalNoSurfaceElements(); segmentId++) {
2085 MFloat* surfCoordinates = &m_surfaceData.surfaceCoordinates(segmentId);
2086
2087 if constexpr(nDim == 3) {
2088 // Geometric Distance & Coordinate Transformation
2089 MFloat distX = coord[0] - surfCoordinates[0];
2090 MFloat distY = coord[1] - surfCoordinates[1];
2091 MFloat distZ = coord[2] - surfCoordinates[2];
2092 transformCoordinatesToFlowDirection3D(&distX, &distY, &distZ);
2093
2094 // Effective Acoustic Distance
2095 const MFloat R = sqrt(distX * distX + betaSq * (distY * distY + distZ * distZ));
2096 const MFloat dist = (R - mach * distX) / betaSq;
2097
2098 // Find the minimum distance of all segments
2099 distMinLocal = (dist < distMinLocal) ? dist : distMinLocal;
2100 } else if constexpr(nDim == 2) {
2101 TERMM(1, "Time Domain FW-H not implimented in 2D: Requires free space Green function in 2D");
2102 } // End of the dimension switch
2103 } // End of surface loop
2104
2105 distMinObservers[k] = distMinLocal;
2106 } // End of observer loop
2107
2108 MPI_Allreduce(MPI_IN_PLACE, &distMinObservers[0], globalNoObservers(), type_traits<MFloat>::mpiType(), MPI_MIN,
2109 mpiComm(), AT_, "MPI_IN_PLACE", "distMinObservers");
2110
2111 if(m_fwhTimeShiftType == 2) {
2112 const MFloat disMinGlobal = *std::min_element(distMinObservers, distMinObservers + globalNoObservers());
2113 std::fill(distMinObservers, distMinObservers + globalNoObservers(), disMinGlobal);
2114 if(domainId() == 0) {
2115 std::cout << "Apply universal time shift for all observers = " << disMinGlobal << std::endl;
2116 }
2117 }
2118}
2119
2122template <MInt nDim>
2123void AcaSolver<nDim>::interpolateFromSourceToObserverTime(MFloat distMinObservers, MFloat* const p_perturbedFWH) {
2124 const MFloat inverseFourPI = 1.0 / 4.0 / PI;
2125
2126 // Loop over all the observer timeSteps
2127 maia::parallelFor<false>(0, noSamples(), [=](MInt t) {
2128 // Set the observer time
2129 MFloat timeObserver = t * m_dt + distMinObservers;
2130
2131 // initialize surface integral
2132 MFloat sumFwhPP = 0.0;
2133
2134 // Surface element loop
2135 for(MInt segmentId = 0; segmentId < totalNoSurfaceElements(); segmentId++) {
2136 // initialize interpolation value
2137 MFloat interpolateFwhPP = 0.0;
2138 MInt t_lower = 0;
2139 MInt t_upper = noSamples() - 1;
2140
2141 // Read the observer lower/upper bound
2142 MFloat timeObserver_lower = m_surfaceData.variables(segmentId, FWH_TIME::timeObs, t_lower);
2143 MFloat timeObserver_upper = m_surfaceData.variables(segmentId, FWH_TIME::timeObs, t_upper);
2144
2145 // Search in FWH_TIME::timeObs
2146 if(timeObserver < timeObserver_lower || timeObserver > timeObserver_upper) {
2147 // In case the timeObserver is out of bound
2148 interpolateFwhPP = 0.0;
2149 } else if(approx(timeObserver, timeObserver_lower, MFloatEps)) {
2150 // In case the timeObserver is at the lower bound
2151 interpolateFwhPP = m_surfaceData.variables(segmentId, FWH_TIME::fwhPP, t_lower);
2152 } else if(approx(timeObserver, timeObserver_upper, MFloatEps)) {
2153 // In case the timeObserver is at the upper bound
2154 interpolateFwhPP = m_surfaceData.variables(segmentId, FWH_TIME::fwhPP, t_upper);
2155 } else {
2156 // In the case of: timeObserver_lower < timeObserver < timeObserver_upper
2157 // Use binary search algorithm to search
2158
2159 MInt t_range = t_upper - t_lower;
2160 do {
2161 // Compute t_mid
2162 const MFloat t_mid = (t_range % 2 == 0) ? ((t_lower + t_upper) / 2) : ((t_lower + t_upper - 1) / 2);
2163 const MFloat timeObserver_mid = m_surfaceData.variables(segmentId, FWH_TIME::timeObs, t_mid);
2164
2165 if(approx(timeObserver, timeObserver_mid, MFloatEps)) {
2166 // In case the timeObserver is at the middle
2167 interpolateFwhPP = m_surfaceData.variables(segmentId, FWH_TIME::fwhPP, t_mid);
2168 break;
2169 }
2170
2171 // Update t_upper and t_lower
2172 if(timeObserver < timeObserver_mid) {
2173 t_upper = t_mid;
2174 } else if(timeObserver > timeObserver_mid) {
2175 t_lower = t_mid;
2176 }
2177 t_range = t_upper - t_lower;
2178
2179 // For the smallest un-dividable region, do linear interpolation to get the observer fwhPP
2180 if(t_range == 1) {
2181 timeObserver_lower = m_surfaceData.variables(segmentId, FWH_TIME::timeObs, t_lower);
2182 timeObserver_upper = m_surfaceData.variables(segmentId, FWH_TIME::timeObs, t_upper);
2183 const MFloat fwhPP_lower = m_surfaceData.variables(segmentId, FWH_TIME::fwhPP, t_lower);
2184 const MFloat fwhPP_upper = m_surfaceData.variables(segmentId, FWH_TIME::fwhPP, t_upper);
2185 const MFloat fwhPP_slop = (fwhPP_upper - fwhPP_lower) / (timeObserver_upper - timeObserver_lower);
2186 interpolateFwhPP = fwhPP_lower + (timeObserver - timeObserver_lower) * fwhPP_slop;
2187 break;
2188 }
2189 } while(t_range > 1);
2190 } // End of the search in FWH_TIME::timeObs
2191
2192 sumFwhPP += interpolateFwhPP;
2193 } // End of the surface loop
2194
2195 p_perturbedFWH[t] = inverseFourPI * sumFwhPP;
2196 }); // End of the observer time loop (parallel)
2197}
2198
2202template <MInt nDim>
2203void AcaSolver<nDim>::combineSurfaceIntegrals(const MInt globalObserverId, MFloat* const p_complexVariables) {
2204 TRACE();
2205 RECORD_TIMER_START(m_timers[Timers::CombineSurfaceIntegrals]);
2206
2207 // Data is stored in unconvenient way (globalObserverId, 0) so that Allreduce won't work. Put Data in linear
2208 // sequence --> Allreduce --> Back in the convenient data structure
2209 // (globalObserverId, 0)
2210
2211 // First: Copy all Observerdata to a databuffer
2212 // obsDataSize is noSamples, because we have imaginary and real part but only for half of samples
2213 // (all positive frequencies including zero)
2214 constexpr MInt noRealAndIm = 2;
2215 const MInt halfNoSamplesRealAndIm = (noSamples() / 2 + 1) * noRealAndIm;
2216 const MInt dataBuffSize = halfNoSamplesRealAndIm * 1.0;
2217 std::vector<MFloat> obsDataBuff(dataBuffSize, 0.0);
2218
2219 const MInt trgDomain = getObserverDomainId(globalObserverId);
2220 const MInt observerId = a_localObserverId(globalObserverId);
2221
2222 TERMM_IF_COND(trgDomain == domainId() && (observerId < 0 || observerId > noObservers()),
2223 "Error in observer partitioning!");
2224
2225 // The actual copying: Whole observer Data from observer "globalObserverId" to the position globalObserverId *
2226 // obsDataSize in databuffer
2227 std::copy_n(p_complexVariables, halfNoSamplesRealAndIm, obsDataBuff.data());
2228 // obsDataBuff now contains all complex pressure values of all observers in linear sequence
2229
2230 if(domainId() == trgDomain) {
2231 // Sums up all parts of the surface integral and distributes the results back to all processes.
2232 MPI_Reduce(MPI_IN_PLACE, obsDataBuff.data(), dataBuffSize, type_traits<MFloat>::mpiType(), MPI_SUM, trgDomain,
2233 mpiComm(), AT_, "MPI_IN_PLACE", "obsDataBuff");
2234 // obsDataBuff now contains the FWH-Integral value of each observer and each frequency in linear
2235 // sequence.
2236
2237 // Split the data buffer into the convenient data structure
2238 std::copy_n(obsDataBuff.data(), halfNoSamplesRealAndIm, &m_observerData.complexVariables(observerId, 0));
2239 } else {
2240 MPI_Reduce(obsDataBuff.data(), obsDataBuff.data(), dataBuffSize, type_traits<MFloat>::mpiType(), MPI_SUM, trgDomain,
2241 mpiComm(), AT_, "MPI_IN_PLACE", "obsDataBuff");
2242 }
2243
2244 // At first the segments were split up on processers. Each processes computed
2245 // the integral for all observers for his segments. Then all integral were
2246 // summed up. Now, all processes have the correct (local) observer data.
2247
2248 RECORD_TIMER_STOP(m_timers[Timers::CombineSurfaceIntegrals]);
2249}
2250
2252template <MInt nDim>
2253void AcaSolver<nDim>::combineSurfaceIntegralsInTime(const MInt globalObserverId, MFloat* const p_perturbedFWH) {
2254 TRACE();
2255
2256 // Data is stored in unconvenient way (globalObserverId, 0) so that Allreduce won't work. Put Data in linear
2257 // sequence --> Allreduce --> Back in the convenient data structure (globalObserverId, 0)
2258
2259 // First: Copy all Observerdata to a databuffer
2260 const MInt dataBuffSize = noSamples();
2261 std::vector<MFloat> obsDataBuff(dataBuffSize, 0.0);
2262
2263 const MInt trgDomain = getObserverDomainId(globalObserverId);
2264 const MInt observerId = a_localObserverId(globalObserverId);
2265
2266 TERMM_IF_COND(trgDomain == domainId() && (observerId < 0 || observerId > noObservers()),
2267 "Error in observer partitioning!");
2268
2269 // The actual copying: Whole observer Data from observer "globalObserverId" to
2270 // the position globalObserverId * obsDataSize in databuffer
2271 std::copy_n(p_perturbedFWH, dataBuffSize, obsDataBuff.data());
2272 // obsDataBuff now contains all complex pressure values of all observers in linear sequence
2273
2274 if(domainId() == trgDomain) {
2275 // Sums up all parts of the surface integral and distributes the results back to all processes.
2276 MPI_Reduce(MPI_IN_PLACE, obsDataBuff.data(), dataBuffSize, type_traits<MFloat>::mpiType(), MPI_SUM, trgDomain,
2277 mpiComm(), AT_, "MPI_IN_PLACE", "obsDataBuff");
2278 // obsDataBuff now contains the FWH-Integral value of each observer and each frequency in linear sequence.
2279
2280 // Split the data buffer into the convenient data structure
2281 std::copy_n(obsDataBuff.data(), dataBuffSize, &m_observerData.variables(observerId, 0));
2282 } else {
2283 MPI_Reduce(obsDataBuff.data(), obsDataBuff.data(), dataBuffSize, type_traits<MFloat>::mpiType(), MPI_SUM, trgDomain,
2284 mpiComm(), AT_, "MPI_IN_PLACE", "obsDataBuff");
2285 }
2286
2287 // At first the segments were split up on processers. Each processes computed
2288 // the integral for all observers for his segments. Then all integral were
2289 // summed up. Now, all processes have the correct (local) observer data.
2290}
2291
2293template <MInt nDim>
2295 printMessage("- Calculate observer signals in frequency domain");
2296 ScratchSpace<MFloat> complexVariablesBuffer(2 * noSamples(), AT_, "complexVariablesBuffer");
2297 complexVariablesBuffer.fill(0.0);
2298 MFloat* const p_complexVariables = complexVariablesBuffer.data();
2299 for(MInt globalObserverId = 0; globalObserverId < globalNoObservers(); globalObserverId++) {
2300 std::array<MFloat, nDim> coord = getGlobalObserverCoordinates(globalObserverId);
2301 calcSurfaceIntegralsForObserver(coord, p_complexVariables);
2302 applySymmetryBc(coord, p_complexVariables);
2303 combineSurfaceIntegrals(globalObserverId, p_complexVariables);
2304 }
2305}
2306
2308template <MInt nDim>
2310 // Set memory for min observer distance
2311 ScratchSpace<MFloat> distMinBuffer(globalNoObservers(), AT_, "distMinBuffer");
2312 distMinBuffer.fill(0.0);
2313 MFloat* const distMinObservers = distMinBuffer.data();
2314
2315 // Set memory for observer pressure signal
2316 ScratchSpace<MFloat> perturbedPressureBuffer(noSamples(), AT_, "perturbedPressureBuffer");
2317 perturbedPressureBuffer.fill(0.0);
2318 MFloat* const p_perturbedFWH = perturbedPressureBuffer.data();
2319
2320 printMessage("- Calculate observer signals in time domain");
2321 calcMinDistanceForObservers(distMinObservers);
2322
2323 for(MInt globalObserverId = 0; globalObserverId < globalNoObservers(); globalObserverId++) {
2324 // m_log << "Compute for observer = " << globalObserverId << "/" << globalNoObservers() << std::endl;
2325 calcSurfaceIntegralsAtSourceTime(globalObserverId);
2326 interpolateFromSourceToObserverTime(distMinObservers[globalObserverId], p_perturbedFWH);
2327 combineSurfaceIntegralsInTime(globalObserverId, p_perturbedFWH);
2328 }
2329}
2330
2332template <MInt nDim>
2334 TRACE();
2335 const MInt minNoObsPerDomain = globalNoObservers() / noDomains();
2336 const MInt remainderObs = globalNoObservers() % noDomains();
2337 if(domainId() < remainderObs) {
2338 m_noObservers = minNoObsPerDomain + 1;
2339 m_offsetObserver = domainId() * (minNoObsPerDomain + 1);
2340 } else {
2341 m_noObservers = minNoObsPerDomain;
2342 m_offsetObserver = domainId() * minNoObsPerDomain + remainderObs;
2343 }
2344}
2345
2347template <MInt nDim>
2349 TRACE();
2350 RECORD_TIMER_START(m_timers[Timers::BacktransformObservers]);
2351 printMessage("- Backtransforming the observer signals");
2352
2353 // Data buffer for backtransformed pressure data
2354 MFloatScratchSpace backtransform(noSamples() * 2, AT_, "backtransform");
2355
2356 // BACKTRANSFORM
2357 for(MInt obs = 0; obs < noObservers(); obs++) {
2358 // Copy the first calculated half to the other half starting from behind going to the middle
2359 // (since it is a two-sided frequency plot)
2360 for(MInt i = 1; i < noSamples() / 2; i++) {
2361 m_observerData.complexVariables(obs, 0, noSamples() - i, 0) = m_observerData.complexVariables(obs, 0, i, 0);
2362 m_observerData.complexVariables(obs, 0, noSamples() - i, 1) =
2363 -1.0 * m_observerData.complexVariables(obs, 0, i, 1);
2364 }
2365
2366 std::fill_n(&backtransform[0], 2 * noSamples(), 0.0);
2367 // Backtransformation
2368 if(m_FastFourier == true && m_transformationType == 1) {
2369 FastFourierTransform(&m_observerData.complexVariables(obs, 0), noSamples(), -1, &backtransform[0], false);
2370 } else if(m_FastFourier == false || (m_FastFourier == true && m_transformationType == 0)) {
2371 DiscreteFourierTransform(&m_observerData.complexVariables(obs, 0), noSamples(), -1, &backtransform[0], false);
2372 }
2373
2374 // Storing the results in observer.variables since it is back in time domain (only real part)
2375 for(MInt i = 0; i < noSamples(); i++) {
2376 m_observerData.variables(obs, 0, i) = backtransform[2 * i];
2377 }
2378 }
2379
2380 RECORD_TIMER_STOP(m_timers[Timers::BacktransformObservers]);
2381}
2382
2383
2387template <MInt nDim>
2389 TRACE();
2390 RECORD_TIMER_START(m_timers[Timers::SaveObserverSignals]);
2391
2392 printMessage("- Save observer signals");
2393
2394 using namespace maia::parallel_io;
2395 const MString fileName = outputDir() + m_outputFilePrefix + "observerData" + ParallelIo::fileExt();
2396
2397 ParallelIo file(fileName, PIO_REPLACE, mpiComm());
2398
2399 ParallelIo::size_type dimSizesCoordinates[] = {globalNoObservers(), nDim};
2400 file.defineArray(PIO_FLOAT, "coordinates", 2, &dimSizesCoordinates[0]);
2401
2402 file.defineArray(PIO_FLOAT, "time", noSamples());
2403 file.defineArray(PIO_FLOAT, "frequency", noSamples());
2404
2405 ParallelIo::size_type dimSizesVarTime[] = {globalNoObservers(), noSamples()};
2406 file.defineArray(PIO_FLOAT, "pressure_time", 2, &dimSizesVarTime[0]);
2407
2408 ParallelIo::size_type dimSizesVarFreq[] = {globalNoObservers(), 2 * noSamples()};
2409 file.defineArray(PIO_FLOAT, "pressure_frequency", 2, &dimSizesVarFreq[0]);
2410
2411 // Store main parameters/configuration as attributes
2412 const MString acousticMethod = (m_acousticMethod == FWH_METHOD) ? "FWH" : "FWH_APE";
2413 file.setAttribute(acousticMethod, "acousticMethod");
2414 file.setAttribute(m_Ma, "Ma");
2415 if(!approx(m_Ma, m_MaDim, MFloatEps)) {
2416 file.setAttribute(m_MaDim, "Ma_dim");
2417 }
2418 file.setAttributes(m_MaVec, "Ma_i", nDim);
2419 // Sample attributes
2420 file.setAttribute(m_noSamples, "noSamples");
2421 file.setAttribute(m_sampleStride, "sampleStride");
2422 file.setAttribute(m_sampleOffset, "sampleOffset");
2423 file.setAttribute(m_dt, "dt");
2424 // Windowing
2425 file.setAttribute(WINDOW::windowNames[m_windowType], "windowType");
2426 file.setAttribute(m_windowNormFactor, "windowNormFactor");
2427 // Surface attributes
2428 for(MInt sId = 0; sId < noSurfaces(); sId++) {
2429 file.setAttribute(m_surfaceInputFileName[sId], "inputFileName_" + std::to_string(m_surfaceIds[sId]));
2430 file.setAttribute(m_noSurfaceElementsGlobal[sId], "noSurfaceElements_" + std::to_string(m_surfaceIds[sId]));
2431 file.setAttribute(m_surfaceWeightingFactor[sId], "surfaceWeight_" + std::to_string(m_surfaceIds[sId]));
2432 }
2433 file.setAttribute(m_observerFileName, "observerFileName");
2434
2435 // Store also observer coordinates to have everything in one file
2436 if(domainId() == 0) {
2437 file.setOffset(globalNoObservers(), 0, 2);
2438 } else {
2439 file.setOffset(0, 0, 2);
2440 }
2441 file.writeArray(m_globalObserverCoordinates.data(), "coordinates");
2442
2443 // write times and frequencies
2444 if(domainId() == 0) {
2445 file.setOffset(noSamples(), 0);
2446 } else {
2447 file.setOffset(0, 0);
2448 }
2449 file.writeArray(&m_times[0], "time");
2450 file.writeArray(&m_frequencies[0], "frequency");
2451
2452 file.setOffset(noObservers(), observerOffset(), 2);
2453 ScratchSpace<MFloat> pressure_time(std::max(noObservers(), 1), noSamples(), AT_, "pressure_time");
2454 for(MInt obs = 0; obs < noObservers(); obs++) {
2455 for(MInt t = 0; t < noSamples(); t++) {
2456 pressure_time(obs, t) = m_observerData.variables(obs, 0, t);
2457 }
2458 }
2459 file.writeArray(&pressure_time[0], "pressure_time");
2460
2461 file.setOffset(noObservers(), observerOffset(), 2);
2462 ScratchSpace<MFloat> pressure_frequency(std::max(noObservers(), 1), 2 * noSamples(), AT_, "pressure_frequency");
2463 for(MInt obs = 0; obs < noObservers(); obs++) {
2464 for(MInt f = 0; f < noSamples(); f++) {
2465 pressure_frequency(obs, 2 * f) = m_observerData.complexVariables(obs, 0, f, 0);
2466 pressure_frequency(obs, 2 * f + 1) = m_observerData.complexVariables(obs, 0, f, 1);
2467 }
2468 }
2469 file.writeArray(&pressure_frequency[0], "pressure_frequency");
2470
2471 RECORD_TIMER_STOP(m_timers[Timers::SaveObserverSignals]);
2472}
2473
2474
2476template <MInt nDim>
2478 TRACE();
2479 RECORD_TIMER_START(m_timers[Timers::LoadObserverSignals]);
2480 using namespace maia::parallel_io;
2481
2482 const MInt obsOffset = observerOffset();
2483 const MInt obsCount = noObservers();
2484
2485 std::stringstream ss;
2486 ss << "- Load observer signals from file: " << m_acaPostprocessingFile << std::endl;
2487 printMessage(ss.str());
2488 ParallelIo file(m_acaPostprocessingFile, PIO_READ, mpiComm());
2489
2490 // read times and frequencies on rank 0 and broadcast
2491 if(domainId() == 0) {
2492 file.setOffset(noSamples(), 0);
2493 } else {
2494 file.setOffset(0, 0);
2495 }
2496 file.readArray(&m_times[0], "time");
2497 file.readArray(&m_frequencies[0], "frequency");
2498
2499 MPI_Bcast(&m_times[0], noSamples(), type_traits<MFloat>::mpiType(), 0, mpiComm(), AT_, "&m_times[0]");
2500 MPI_Bcast(&m_frequencies[0], noSamples(), type_traits<MFloat>::mpiType(), 0, mpiComm(), AT_, "&m_frequencies[0]");
2501
2502 // Read observer data in time domain
2503 file.setOffset(obsCount, obsOffset, 2);
2504 ScratchSpace<MFloat> pressure_time(std::max(obsCount, 1), noSamples(), AT_, "pressure_time");
2505
2506 file.readArray(&pressure_time[0], "pressure_time");
2507 for(MInt obs = 0; obs < obsCount; obs++) {
2508 for(MInt t = 0; t < noSamples(); t++) {
2509 m_observerData.variables(obs, 0, t) = pressure_time(obs, t);
2510 }
2511 }
2512
2513 // Read observer data in frequency domain
2514 file.setOffset(obsCount, obsOffset, 2);
2515 ScratchSpace<MFloat> pressure_frequency(std::max(obsCount, 1), 2 * noSamples(), AT_, "pressure_frequency");
2516 file.readArray(&pressure_frequency[0], "pressure_frequency");
2517
2518 for(MInt obs = 0; obs < obsCount; obs++) {
2519 for(MInt f = 0; f < noSamples(); f++) {
2520 m_observerData.complexVariables(obs, 0, f, 0) = pressure_frequency(obs, 2 * f);
2521 m_observerData.complexVariables(obs, 0, f, 1) = pressure_frequency(obs, 2 * f + 1);
2522 }
2523 }
2524
2525 RECORD_TIMER_STOP(m_timers[Timers::LoadObserverSignals]);
2526}
2527
2528
2530template <MInt nDim>
2532 TRACE();
2533 RECORD_TIMER_START(m_timers[Timers::Postprocessing]);
2534 printMessage("- Post process observer signals");
2535
2536 // Loop over all postprocessing operations
2537 for(MInt op = 0; op < m_noPostprocessingOps; op++) {
2538 const MInt operation = m_postprocessingOps[op];
2539 switch(operation) {
2540 case PP::RMS_PRESSURE: {
2541 m_post.push_back(std::make_unique<AcaPostProcessingRMS<nDim>>(mpiComm()));
2542 break;
2543 }
2544 case PP::OASPL: {
2545 m_post.push_back(std::make_unique<AcaPostProcessingOASPL<nDim>>(mpiComm()));
2546 break;
2547 }
2548 case PP::SPL: {
2549 m_post.push_back(std::make_unique<AcaPostProcessingSPL<nDim>>(mpiComm()));
2550 break;
2551 }
2552 case PP::pABS: {
2553 m_post.push_back(std::make_unique<AcaPostProcessingPABS<nDim>>(mpiComm()));
2554 break;
2555 }
2556 case PP::calcObsPress: {
2557 TERMM_IF_NOT_COND(m_generateSurfaceData, "Only useful for analytical testcases with generated surface data.");
2558 calcObsPressureAnalytic();
2559 break;
2560 }
2561 default: {
2562 std::cerr << " "
2563 << "skippinginvalid postprocessing operation: " << operation << std::endl;
2564 break;
2565 }
2566 }
2567 }
2568
2569 for(auto&& post : m_post) {
2570 post->init(noObservers(), globalNoObservers(), observerOffset(), noSamples(), m_globalObserverCoordinates.data(),
2571 m_frequencies.data(), outputDir() + m_outputFilePrefix);
2572 for(MInt obs = 0; obs < noObservers(); obs++) {
2573 post->calc(obs, &m_observerData.variables(obs, 0), &m_observerData.complexVariables(obs, 0));
2574 }
2575 post->finish();
2576 }
2577
2578 RECORD_TIMER_STOP(m_timers[Timers::Postprocessing]);
2579}
2580
2581// Only useful for analytical test cases
2582template <MInt nDim>
2584 TRACE();
2585
2586 const MInt noVars = (m_acousticMethod == FWH_METHOD) ? nDim + 2 : nDim + 1;
2587 MFloatScratchSpace vars(noVars, noObservers(), noSamples(), AT_, "varP");
2588 vars.fill(0.0);
2589
2590 for(MInt obsId = 0; obsId < noObservers(); obsId++) {
2591 // Pointers to pass to functions
2592 const MFloat* obsCoord = &m_observerData.observerCoordinates(obsId);
2593 SourceVars sourceVars;
2594 if(m_acousticMethod == FWH_METHOD) {
2595 sourceVars.p = &vars(FWH::P, obsId, 0);
2596 sourceVars.u = &vars(FWH::U, obsId, 0);
2597 sourceVars.v = &vars(FWH::V, obsId, 0);
2598 sourceVars.w = &vars(FWH::W, obsId, 0);
2599 sourceVars.rho = &vars(FWH::RHO, obsId, 0);
2600 } else {
2601 sourceVars.p = &vars(FWH_APE::PP, obsId, 0);
2602 sourceVars.u = &vars(FWH_APE::UP, obsId, 0);
2603 sourceVars.v = &vars(FWH_APE::VP, obsId, 0);
2604 sourceVars.w = &vars(FWH_APE::WP, obsId, 0);
2605 }
2606
2607 if constexpr(nDim == 2) {
2608 switch(m_sourceType) {
2609 case 0: {
2610 genMonopoleAnalytic2D(obsCoord, m_sourceParameters, sourceVars);
2611 break;
2612 }
2613 case 1: {
2614 genDipoleAnalytic2D(obsCoord, m_sourceParameters, sourceVars);
2615 break;
2616 }
2617 case 2: {
2618 genQuadrupoleAnalytic2D(obsCoord, m_sourceParameters, sourceVars);
2619 break;
2620 }
2621 case 3: {
2622 genVortexConvectionAnalytic2D(obsCoord, m_sourceParameters, sourceVars);
2623 break;
2624 }
2625 default: {
2626 TERMM(1, "source type not implemented");
2627 break;
2628 }
2629 }
2630 } else {
2631 switch(m_sourceType) {
2632 case 0: {
2633 genMonopoleAnalytic3D(obsCoord, m_sourceParameters, sourceVars);
2634 break;
2635 }
2636 case 1: {
2637 genDipoleAnalytic3D(obsCoord, m_sourceParameters, sourceVars);
2638 break;
2639 }
2640 case 2: {
2641 genQuadrupoleAnalytic3D(obsCoord, m_sourceParameters, sourceVars);
2642 break;
2643 }
2644 default: {
2645 TERMM(1, "source type not implemented");
2646 break;
2647 }
2648 }
2649 }
2650 }
2651
2652 // OUTPUT
2653 using namespace maia::parallel_io;
2654 const MString fileName = outputDir() + m_outputFilePrefix + "obsPressAnalytic" + ParallelIo::fileExt();
2655
2656 ParallelIo file(fileName, PIO_REPLACE, mpiComm());
2657
2658 ParallelIo::size_type dimSizes[] = {globalNoObservers(), noSamples()};
2659 file.defineArray(PIO_FLOAT, "obsPressAnalytic", 2, &dimSizes[0]);
2660
2661 file.setOffset(noObservers(), observerOffset(), 2);
2662 if(m_acousticMethod == FWH_METHOD) {
2663 file.writeArray(&vars(FWH::P, 0, 0), "obsPressAnalytic");
2664 } else {
2665 file.writeArray(&vars(FWH_APE::PP, 0, 0), "obsPressAnalytic");
2666 }
2667}
2668
2671template <MInt nDim>
2673 TRACE();
2674 printMessage("- Init parallelization");
2675 RECORD_TIMER_START(m_timers[Timers::InitParallelization]);
2676
2677 m_noSurfaceElements.resize(noSurfaces());
2678 m_noSurfaceElementsGlobal.resize(noSurfaces());
2679 m_surfaceElementOffsets.resize(noSurfaces());
2680 m_surfaceInputFileName.resize(noSurfaces(), "");
2681
2682 std::vector<MInt> totalNoSegments(noSurfaces());
2683
2684 // TODO labels:ACA maybe do this only on rank 0
2685 if(m_generateSurfaceData) {
2686 for(MInt sId = 0; sId < noSurfaces(); sId++) {
2687 std::vector<MInt> dummyVec{};
2688 // Determine total number of segments for each surface
2689 // const MInt totalNoSegments = readNoSegmentsSurfaceGeneration(sId, dummyVec);
2690 totalNoSegments[sId] = readNoSegmentsSurfaceGeneration(sId, dummyVec);
2691 }
2692 } else {
2693 m_totalNoSamples = -1;
2694
2695 // Determine total number of samples and number of segments for each surface from input data files
2696 for(MInt sId = 0; sId < noSurfaces(); sId++) {
2697 MInt noSamples = -1;
2698 readNoSegmentsAndSamples(sId, totalNoSegments[sId], noSamples, m_surfaceInputFileName[sId]);
2699
2700 // Check that number of samples matches for all surfaces
2701 TERMM_IF_NOT_COND(m_totalNoSamples < 0 || m_totalNoSamples == noSamples,
2702 "number of samples mismatch for different surfaces");
2703 m_totalNoSamples = noSamples;
2704 }
2705
2706 m_noSamples = (m_noSamples == -1) ? m_totalNoSamples : m_noSamples;
2707
2708 // Check noSamples/stride/offset
2709 const MInt usedSampleRange = m_noSamples * m_sampleStride + m_sampleOffset;
2710 TERMM_IF_COND(m_noSamples < 1 || m_sampleStride < 1 || m_sampleOffset < 0 || usedSampleRange > m_totalNoSamples,
2711 "Error: number of samples and stride need to be > 0, offset >= 0 and samples*stride+offset cannot "
2712 "exceed the total number of samples (noSamples="
2713 + std::to_string(m_noSamples) + ", stride=" + std::to_string(m_sampleStride) + ", offset="
2714 + std::to_string(m_sampleOffset) + ", samples*stride+offset=" + std::to_string(usedSampleRange)
2715 + ", totalNoSamples=" + std::to_string(m_totalNoSamples) + ").");
2716
2717 // Only even number of samples
2718 if(m_noSamples % 2 > 0) {
2719 m_noSamples = m_noSamples - 1;
2720 if(domainId() == 0) {
2721 std::cout << "Note: Number of Samples was uneven which is not feasible. The new number of samples is: "
2722 << m_noSamples << std::endl;
2723 }
2724 }
2725 }
2726
2727 // Store global number of segments for all surfaces
2728 std::copy(totalNoSegments.begin(), totalNoSegments.end(), &m_noSurfaceElementsGlobal[0]);
2729
2730 // Determine distribution of surface segments among all processes
2731 const MInt globalNoSegments = std::accumulate(totalNoSegments.begin(), totalNoSegments.end(), 0);
2732
2733 for(MInt sId = 0; sId < noSurfaces(); sId++) {
2734 m_log << "Surface #" << m_surfaceIds[sId] << " has " << totalNoSegments[sId]
2735 << " segments; surface input file name: " << m_surfaceInputFileName[sId]
2736 << "; weighting factor: " << m_surfaceWeightingFactor[sId] << std::endl;
2737 }
2738 m_log << "Global number of segments: " << globalNoSegments << std::endl;
2739 m_log << "Number of samples: " << m_noSamples << " (totalNoSamples = " << m_totalNoSamples
2740 << ", stride = " << m_sampleStride << ", offset = " << m_sampleOffset << ")" << std::endl;
2741
2742 MInt localNoSegments = -1;
2743
2744 // Distribute all segments equally among all domains. This means that a process can have segments
2745 // of different surfaces, e.g., when using only a small number of processes. However, for larger
2746 // production cases it makes sense to limit each process to have only segments of a single surface
2747 // if the I/O of data of different surfaces should be fully parallel.
2748 if(m_allowMultipleSurfacesPerRank) {
2749 // Average number of segments (rounded down)
2750 const MInt avgNoSegments = floor(static_cast<MFloat>(globalNoSegments) / static_cast<MFloat>(noDomains()));
2751 localNoSegments = avgNoSegments;
2752 // Equally distribute missing segments
2753 if(domainId() < globalNoSegments - noDomains() * avgNoSegments) {
2754 localNoSegments += 1;
2755 }
2756 } else { // Restrict each rank to have only segments of one surface to improve performance in production cases
2757 std::vector<MInt> noDomainsPerSurface(noSurfaces());
2758 // Average number of segments
2759 const MFloat avgNoSegments = static_cast<MFloat>(globalNoSegments) / static_cast<MFloat>(noDomains());
2760 // Distribute ranks on surfaces
2761 for(MInt i = 0; i < noSurfaces(); i++) {
2762 noDomainsPerSurface[i] = std::max(1, static_cast<MInt>(std::floor(totalNoSegments[i] / avgNoSegments)));
2763 }
2764 // Assign remaining ranks to the surfaces with the highest number of segments per rank
2765 while(noDomains() - std::accumulate(noDomainsPerSurface.begin(), noDomainsPerSurface.end(), 0) > 0) {
2766 MFloat maxWeight = 0.0;
2767 MInt maxIndex = -1;
2768 for(MInt i = 0; i < noSurfaces(); i++) {
2769 const MFloat weight = totalNoSegments[i] / noDomainsPerSurface[i];
2770 if(weight > maxWeight) {
2771 maxWeight = weight;
2772 maxIndex = i;
2773 }
2774 }
2775 noDomainsPerSurface[maxIndex]++;
2776 }
2777 const MInt noDomainsSum = std::accumulate(noDomainsPerSurface.begin(), noDomainsPerSurface.end(), 0);
2778 TERMM_IF_NOT_COND(noDomainsSum == noDomains(),
2779 "number of domains mismatch " + std::to_string(noDomainsSum) + " " + std::to_string(noDomains()));
2780
2781 // Determine the local surface
2782 MInt localSurfaceId = -1;
2783 MInt localSurfaceDomainId = -1;
2784 for(MInt i = 0, sumDomains = 0; i < noSurfaces(); i++) {
2785 if(sumDomains + noDomainsPerSurface[i] > domainId()) {
2786 localSurfaceId = i;
2787 localSurfaceDomainId = domainId() - sumDomains;
2788 break;
2789 }
2790 sumDomains += noDomainsPerSurface[i];
2791 }
2792
2793 const MInt avgNoSegmentsSurface = floor(totalNoSegments[localSurfaceId] / noDomainsPerSurface[localSurfaceId]);
2794 // Determine local number of segments of this surface
2795 localNoSegments = avgNoSegmentsSurface;
2796 if(localSurfaceDomainId
2797 < totalNoSegments[localSurfaceId] - noDomainsPerSurface[localSurfaceId] * avgNoSegmentsSurface) {
2798 localNoSegments += 1;
2799 }
2800 }
2801
2802 // Communicate number of segments per domain
2803 std::vector<MInt> noSegmentsPerDomain(noDomains());
2804 MPI_Allgather(&localNoSegments, 1, type_traits<MInt>::mpiType(), &noSegmentsPerDomain[0], 1,
2805 type_traits<MInt>::mpiType(), mpiComm(), AT_, "localNoSegments", "noSegmentsPerDomain[0]");
2806
2807 // Sanity check
2808 const MInt globalCount = std::accumulate(noSegmentsPerDomain.begin(), noSegmentsPerDomain.end(), 0);
2809 TERMM_IF_NOT_COND(globalCount == globalNoSegments, "globalNoSegments mismatch");
2810
2811 // Compute domain offsets
2812 std::vector<MInt> domainSegmentOffsets(std::max(noDomains() + 1, 1));
2813 domainSegmentOffsets[0] = 0;
2814 for(MInt i = 1; i < noDomains() + 1; i++) {
2815 domainSegmentOffsets[i] = domainSegmentOffsets[i - 1] + noSegmentsPerDomain[i - 1];
2816 }
2817
2818 MIntScratchSpace noSegments_(noDomains(), noSurfaces(), AT_, "noSegments_");
2819
2820 MInt surfaceOffset = 0;
2821 // Check which surfaces are present on the local rank
2822 for(MInt sId = 0; sId < noSurfaces(); sId++) {
2823 const MInt nextSurfaceOffset = surfaceOffset + totalNoSegments[sId];
2824 const MInt start = domainSegmentOffsets[domainId()];
2825 const MInt end = domainSegmentOffsets[domainId()] + localNoSegments;
2826
2827 MInt noLocalSegments = 0;
2828 MInt localSegmentsOffset = 0;
2829
2830 if(start < surfaceOffset && end >= surfaceOffset) {
2831 // First local segment belongs to a previous surface but last local segment belongs to current
2832 // or a subsequent surface
2833 noLocalSegments = std::min(totalNoSegments[sId], end - surfaceOffset);
2834 localSegmentsOffset = 0;
2835 } else if(start >= surfaceOffset && start < nextSurfaceOffset) {
2836 // First local segment belongs to this surface
2837 noLocalSegments = std::min(nextSurfaceOffset - start, end - start);
2838 localSegmentsOffset = start - surfaceOffset;
2839 }
2840
2841 m_noSurfaceElements[sId] = noLocalSegments;
2842 m_surfaceElementOffsets[sId] = localSegmentsOffset;
2843
2844 // Store in communication buffer
2845 noSegments_(domainId(), sId) = noLocalSegments;
2846
2847 // Set new offset for next surface
2848 surfaceOffset = nextSurfaceOffset;
2849 }
2850
2851 MPI_Allgather(MPI_IN_PLACE, 0, MPI_DATATYPE_NULL, &noSegments_[0], noSurfaces(), type_traits<MInt>::mpiType(),
2852 mpiComm(), AT_, "MPI_IN_PLACE", "noSegments_[0]");
2853
2854 m_mpiCommSurface.resize(noSurfaces());
2855 m_noDomainsSurface.resize(noSurfaces());
2856 m_domainIdSurface.resize(noSurfaces());
2857
2858 // Create MPI communicators for each surface
2859 for(MInt sId = 0; sId < noSurfaces(); sId++) {
2860 MIntScratchSpace domains(noDomains(), AT_, "domains");
2861 MInt noDomains_ = 0;
2862
2863 // Determine list of subdomains with segments of this surface
2864 for(MInt d = 0; d < noDomains(); d++) {
2865 if(noSegments_(d, sId) > 0) {
2866 domains[noDomains_] = d;
2867 noDomains_++;
2868 }
2869 }
2870
2871 m_log << "Create MPI communicator for surface #" << m_surfaceIds[sId] << " with " << noDomains_
2872 << " ranks:" << std::endl;
2873#ifndef NDEBUG
2874 for(MInt d = 0; d < noDomains_; d++) {
2875 m_log << " * local rank #" << d << " global rank #" << domains[d] << " with " << noSegments_(domains[d], sId)
2876 << " segments" << std::endl;
2877 }
2878#endif
2879
2880 // Obtain new group
2881 MPI_Group globalGroup, localGroup;
2882 MPI_Comm_group(mpiComm(), &globalGroup, AT_, "globalGroup");
2883 MPI_Group_incl(globalGroup, noDomains_, &domains[0], &localGroup, AT_);
2884
2885 // Create new communicator and clean up
2886 MPI_Comm_create(mpiComm(), localGroup, &m_mpiCommSurface[sId], AT_,
2887 "m_mpiCommSurface[" + std::to_string(sId) + "]");
2888
2889 MPI_Group_free(&globalGroup, AT_);
2890 MPI_Group_free(&localGroup, AT_);
2891
2892 if(noSurfaceElements(sId) > 0) {
2893 // If surface is active on current domain, determine local rank and number of domains
2894 MPI_Comm_rank(m_mpiCommSurface[sId], &m_domainIdSurface[sId]);
2895 MPI_Comm_size(m_mpiCommSurface[sId], &m_noDomainsSurface[sId]);
2896
2897 TERMM_IF_NOT_COND(m_noDomainsSurface[sId] == noDomains_, "number of domains mismatch");
2898 } else {
2899 // Reset for inactive surface
2900 m_domainIdSurface[sId] = -1;
2901 m_noDomainsSurface[sId] = -1;
2902 }
2903 }
2904
2905 RECORD_TIMER_STOP(m_timers[Timers::InitParallelization]);
2906}
2907
2908
2912template <MInt nDim>
2913inline MString AcaSolver<nDim>::getSurfaceDataFileName(const MInt surfaceId, const MInt fileNo) {
2914 TRACE();
2915 const MString baseFileName = "surface_data_";
2916 const MString baseFileNameSuffix = ".Netcdf";
2917 std::ostringstream os;
2918 os << m_inputDir << baseFileName << std::to_string(m_surfaceIds[surfaceId]) << "_" << std::setw(8)
2919 << std::setfill('0') << fileNo << baseFileNameSuffix;
2920 return (MString)os.str();
2921}
2922
2924template <MInt nDim>
2926 TRACE();
2927 printMessage("- Read surface data");
2928 RECORD_TIMER_START(m_timers[Timers::ReadSurfaceData]);
2929
2930 // Set data sizes in surface data collector
2931 m_surfaceData.setNoVars(m_noVariables);
2932 m_surfaceData.setNoComplexVars(m_noComplexVariables);
2933 m_surfaceData.setNoSamples(m_noSamples);
2934
2935 // Reset surface data collector to total number of local surface elements
2936 const MInt totalNoSurfaceElements_ = totalNoSurfaceElements();
2937 TERMM_IF_COND(totalNoSurfaceElements_ < 1, "Less surface elements than computational ranks is critical.");
2938 m_surfaceData.reset(totalNoSurfaceElements_);
2939
2940 // Set storage size for times and frequencies
2941 m_times.resize(noSamples());
2942 m_frequencies.resize(noSamples());
2943
2944 // Read in the surface data and store in m_surfaceData
2945 // or generate the surface elements and the corresponding analytical input data
2946 if(m_generateSurfaceData) {
2947 generateSurfaces();
2948 generateSurfaceData();
2949
2950 // Testing: store generated data
2951 for(MInt sId = 0; sId < noSurfaces(); sId++) {
2952 if(hasSurface(sId) && m_storeSurfaceData) {
2953 storeSurfaceData(sId);
2954 }
2955 }
2956 } else if(m_acaPostprocessingMode) {
2957 // Nothing to do if just postprocessing is enabled
2958 } else {
2959 for(MInt sId = 0; sId < noSurfaces(); sId++) {
2960 // Read data if surface present on local rank
2961 if(hasSurface(sId)) {
2962 readSurfaceDataFromFile(sId);
2963
2964 // Testing: store data for validating the I/O
2965 if(m_storeSurfaceData) {
2966 storeSurfaceData(sId);
2967 }
2968 }
2969 }
2970
2971 { // Compare the loaded times on all ranks
2972 ScratchSpace<MFloat> timesBuffer(noSamples(), AT_, "timesBuffer");
2973 std::copy_n(&m_times[0], noSamples(), &timesBuffer[0]);
2974
2975 MPI_Bcast(&timesBuffer[0], noSamples(), type_traits<MFloat>::mpiType(), 0, mpiComm(), AT_, "&timesBuffer[0]");
2976
2977 for(MInt i = 0; i < noSamples(); i++) {
2978 TERMM_IF_COND(!approx(timesBuffer[i], m_times[i], MFloatEps), "Error: times for surfaces do not match.");
2979 }
2980 }
2981
2982 // The simulation non-dimensionalises with different variables! For the FWH simulation one needs
2983 // to change how the numbers are non-dimensionalized
2984 changeDimensionsSurfaceData();
2985 computeDt();
2986 }
2987
2988 RECORD_TIMER_STOP(m_timers[Timers::ReadSurfaceData]);
2989}
2990
2991
2993template <MInt nDim>
2994void AcaSolver<nDim>::readNoSegmentsAndSamples(const MInt surfaceId, MInt& noSegments, MInt& noSamples,
2995 MString& inputFileName) {
2996 TRACE();
2997 using namespace maia::parallel_io;
2998
2999 if(m_useMergedInputFile) {
3009 const MString fileName = m_inputDir
3010 + Context::getSolverProperty<MString>(
3011 "surfaceDataFileName_" + std::to_string(m_surfaceIds[surfaceId]), m_solverId, AT_);
3012 ParallelIo file(fileName, PIO_READ, mpiComm());
3013
3014 noSegments = -1;
3015 noSamples = -1;
3016
3017 for(auto& var : m_inputVars) {
3018 const MString varName = var.first;
3019
3020 std::vector<MLong> dims = file.getArrayDims(varName);
3021
3022 TERMM_IF_NOT_COND(noSegments < 0 || dims[0] == noSegments, "number of segments mismatch between variables");
3023 TERMM_IF_NOT_COND(noSamples < 0 || dims[1] == noSamples, "number of samples mismatch between variables");
3024
3025 noSegments = dims[0];
3026 noSamples = dims[1];
3027 }
3028
3029 // Read surface input file name if stored as attribute in file
3030 if(file.hasAttribute("inputFileName")) {
3031 file.getAttribute(&inputFileName, "inputFileName");
3032 }
3033 } else {
3034 noSamples = 0;
3035 noSegments = 0;
3036 const MString baseFileName = "surface_data_";
3037 const MString baseFileNameSuffix = ".Netcdf";
3038 const MString varName = "pointStates";
3039 for(MInt fileNo = m_inputFileIndexStart; fileNo < m_inputFileIndexEnd + 1; fileNo++) {
3040 const MString fileName = getSurfaceDataFileName(surfaceId, fileNo);
3041 ParallelIo file(fileName, PIO_READ, mpiComm());
3042
3043 std::vector<MLong> dims = file.getArrayDims(varName);
3044
3045 TERMM_IF_COND(noSegments != dims[0] && noSegments != 0, "number of segments mismatch between files");
3046 TERMM_IF_COND(dims[2] < (MInt)m_inputVars.size(), "number of input vars > number of variables in file");
3047
3048 noSegments = dims[0];
3049 noSamples += dims[1];
3050
3051 // TODO labels:ACA sanity check: if variables in input file are the same as requested
3052
3053 // Read surface input file name if stored as attribute in file
3054 if(file.hasAttribute("inputFileName", varName)) {
3055 file.getAttribute(&inputFileName, "inputFileName", varName);
3056 }
3057 }
3058 }
3059}
3060
3061
3063template <MInt nDim>
3065 TRACE();
3066 using namespace maia::parallel_io;
3067
3068 const MInt count = m_noSurfaceElements[surfaceId];
3069 const MInt offset = m_surfaceElementOffsets[surfaceId];
3070 const MInt surfaceOffset = localSurfaceOffset(surfaceId);
3071
3072 const MString measureName = (nDim == 3) ? "area" : "length";
3073
3074 // Create surface elements and store its data in m_surfaceData
3075 auto createSurfaceElements = [&](const MInt l_surfaceId,
3076 const MFloatScratchSpace& l_measure,
3077 const MFloatScratchSpace& l_coordinates,
3078 const MFloatScratchSpace& l_normalVector) {
3079 MFloat totalSurfaceMeasure = 0.0;
3080 MFloat totalSurfaceMeasureWeighted = 0.0;
3081
3082 const MFloat weight = m_surfaceWeightingFactor[l_surfaceId];
3083 for(MInt i = 0; i < count; i++) {
3084 const MInt id = surfaceOffset + i;
3085 // Add a new surface element
3086 m_surfaceData.append();
3087 // Check that the total size of m_surfaceData is correct
3088 TERMM_IF_NOT_COND(m_surfaceData.size() == id + 1, "m_surfaceData size mismatch.");
3089
3090 MFloat* surfCoordinates = &m_surfaceData.surfaceCoordinates(id);
3091 MFloat* surfNormal = &m_surfaceData.surfaceNormal(id);
3092
3093 // Store segment area/length (weighted with surface averaging factor)
3094 m_surfaceData.surfaceArea(id) = weight * l_measure[i];
3095 totalSurfaceMeasure += l_measure[i];
3096 totalSurfaceMeasureWeighted += (weight * l_measure[i]);
3097
3098 // Set coordinates and normal vector
3099 for(MInt j = 0; j < nDim; j++) {
3100 surfCoordinates[j] = l_coordinates[i * nDim + j];
3101 surfNormal[j] = l_normalVector[i * nDim + j];
3102 }
3103 }
3104
3105 // Compute total area/length of surface
3106 MPI_Allreduce(MPI_IN_PLACE, &totalSurfaceMeasure, 1, type_traits<MFloat>::mpiType(), MPI_SUM,
3107 m_mpiCommSurface[surfaceId], AT_, "MPI_IN_PLACE", "totalSurfaceMeasure");
3108 MPI_Allreduce(MPI_IN_PLACE, &totalSurfaceMeasureWeighted, 1, type_traits<MFloat>::mpiType(), MPI_SUM,
3109 m_mpiCommSurface[surfaceId], AT_, "MPI_IN_PLACE", "totalSurfaceMeasureWeighted");
3110 if(m_domainIdSurface[surfaceId] == 0) {
3111 std::cout << "Total " << measureName << " of surface #" << surfaceId << ": " << totalSurfaceMeasure
3112 << " (weighted: " << totalSurfaceMeasureWeighted << ")" << std::endl;
3113 }
3114 };
3115
3116 if(m_useMergedInputFile) {
3117 const MString fileName = m_inputDir
3118 + Context::getSolverProperty<MString>(
3119 "surfaceDataFileName_" + std::to_string(m_surfaceIds[surfaceId]), m_solverId, AT_);
3120 ParallelIo file(fileName, PIO_READ, m_mpiCommSurface[surfaceId]);
3121
3122 MFloatScratchSpace coordinates(count * nDim, AT_, "coordinates");
3123 MFloatScratchSpace normalVector(count * nDim, AT_, "normalVector");
3124 MFloatScratchSpace measure(count, AT_, "measure");
3125
3126 // Read surface area/length
3127 file.setOffset(count, offset);
3128 file.readArray(&measure[0], measureName);
3129
3130 // Read coordinates and normal vectors
3131 file.setOffset(count, offset, 2);
3132 file.readArray(&coordinates[0], "coordinates");
3133 file.readArray(&normalVector[0], "normalVector");
3134
3135 // Create surface elements and store its data in m_surfaceData
3136 createSurfaceElements(surfaceId, measure, coordinates, normalVector);
3137
3138 // Read times only on surface local rank 0 and broadcast to other ranks in this communicator
3139 ScratchSpace<MFloat> timesBuffer(m_totalNoSamples, AT_, "timesBuffer");
3140 if(m_domainIdSurface[surfaceId] == 0) {
3141 // Read full array, then copy selected range to m_times
3142 file.setOffset(m_totalNoSamples, 0);
3143 file.readArray(&timesBuffer[0], "time");
3144
3145 // Check the times with those of other surfaces already loaded on this rank (before overwriting them), later
3146 // cross-check the times with all surfaces
3147 if(m_hasTimes) {
3148 for(MInt i = 0; i < noSamples(); i++) {
3149 TERMM_IF_COND(!approx(timesBuffer[m_sampleOffset + i * m_sampleStride], m_times[i], MFloatEps),
3150 "Error: local times for surfaces do not match.");
3151 }
3152 }
3153
3154 // Store times in m_times
3155 if(m_sampleStride == 1) {
3156 std::copy_n(&timesBuffer[m_sampleOffset], noSamples(), &m_times[0]);
3157 } else {
3158 for(MInt i = 0; i < noSamples(); i++) {
3159 m_times[i] = timesBuffer[m_sampleOffset + i * m_sampleStride];
3160 }
3161 }
3162 } else {
3163 file.setOffset(0, 0);
3164 file.readArray(&timesBuffer[0], "time");
3165 }
3166
3167 MPI_Bcast(&m_times[0], noSamples(), type_traits<MFloat>::mpiType(), 0, m_mpiCommSurface[surfaceId], AT_,
3168 "&m_times[0]");
3169 m_hasTimes = true;
3170
3171 // Set 2D offset (second dimension is the time axis which is set internally to read all samples)
3172 // TODO labels:ACA,IO directly read only selected sample range once this is supported by the ParallelIO class
3173 file.setOffset(count, offset, 2);
3174
3175 // Data buffer
3176 ScratchSpace<MFloat> varData(count, m_totalNoSamples, AT_, "varData");
3177 // Read datasets and store in m_surfaceData
3178 for(auto& var : m_inputVars) {
3179 const MString varName = var.first;
3180
3181 if(m_domainIdSurface[surfaceId] == 0) {
3182 std::cout << "Surface #" << m_surfaceIds[surfaceId] << ": Loading variable: " << varName << std::endl;
3183 }
3184
3185 file.readArray(&varData[0], varName);
3186
3187 const MInt varIndex = var.second;
3188 for(MInt i = 0; i < count; i++) {
3189 const MInt id = surfaceOffset + i;
3190 for(MInt t = 0; t < noSamples(); t++) {
3191 m_surfaceData.variables(id, varIndex, t) = varData(i, m_sampleOffset + t * m_sampleStride);
3192 }
3193 }
3194 }
3195 } else { // ! m_useMergedInputFile -------------------------------------------
3196 TERMM_IF_COND(m_sampleOffset > 0, "Sample offset for multiple input files not supported yet");
3197 TERMM_IF_COND(m_sampleStride > 1, "Sample stride for multiple input files not supported yet");
3198
3199 const MInt noDom = m_noDomainsSurface[surfaceId];
3200 std::vector<MInt> sendCount(noDom);
3201 std::vector<MInt> sendCountNdim(noDom);
3202 std::vector<MInt> displs(noDom);
3203 std::vector<MInt> displsNdim(noDom);
3204 {
3205 std::fill(sendCount.begin(), sendCount.end(), 0);
3206 std::fill(displs.begin(), displs.end(), 0);
3207 sendCount[m_domainIdSurface[surfaceId]] = count;
3208 displs[m_domainIdSurface[surfaceId]] = offset;
3209 MPI_Allreduce(MPI_IN_PLACE, sendCount.data(), sendCount.size(), MPI_INT, MPI_SUM, m_mpiCommSurface[surfaceId],
3210 AT_, "MPI_IN_PLACE", "sendCount");
3211 MPI_Allreduce(MPI_IN_PLACE, displs.data(), displs.size(), MPI_INT, MPI_SUM, m_mpiCommSurface[surfaceId], AT_,
3212 "MPI_IN_PLACE", "displs");
3213 for(MInt i = 0; i < (MInt)sendCount.size(); i++) {
3214 sendCountNdim[i] = sendCount[i] * nDim;
3215 displsNdim[i] = displs[i] * nDim;
3216 }
3217 }
3218 const MInt noTotalCount = std::accumulate(sendCount.begin(), sendCount.end(), 0);
3219
3220 //--Read sortIndex from first input file
3221 std::vector<MInt> sortIndex;
3222 {
3223 auto readSortIndex = [&](const MInt fileNo, std::vector<MInt>& sortIndex_) {
3224 //-Open the correct sample file
3225 const MString fileName = getSurfaceDataFileName(surfaceId, fileNo);
3226 ParallelIo file(fileName, PIO_READ, m_mpiCommSurface[surfaceId]);
3227 //-First read the famous sortIndex_ from first file
3228 const MString varName = "sortIndex";
3229 if(m_domainIdSurface[surfaceId] == 0) {
3230 sortIndex_.resize(noTotalCount);
3231 file.setOffset(noTotalCount, 0);
3232 file.readArray(sortIndex_.data(), varName);
3233 } else {
3234 sortIndex_.resize(0);
3235 file.setOffset(0, 0);
3236 file.readArray(sortIndex_.data(), varName);
3237 }
3238 };
3239 readSortIndex(m_inputFileIndexStart, sortIndex);
3240 // Sanity check: Is sort index the same for all input files?
3241 std::vector<MInt> tmpSortIndex(sortIndex.size());
3242 for(MInt fileNo = 1 + m_inputFileIndexStart; fileNo < m_inputFileIndexEnd + 1; fileNo++) {
3243 readSortIndex(fileNo, tmpSortIndex);
3244 if(!std::equal(sortIndex.begin(), sortIndex.end(), tmpSortIndex.begin())) {
3245 std::stringstream ss;
3246 ss << "SortIndex varies between ";
3247 ss << getSurfaceDataFileName(surfaceId, m_inputFileIndexStart);
3248 ss << " and ";
3249 ss << getSurfaceDataFileName(surfaceId, fileNo);
3250 TERMM(1, ss.str());
3251 }
3252 }
3253 }
3254
3255 //--Read geometry data from separate file
3256 {
3257 MFloatScratchSpace measure(count, AT_, "measure");
3258 MFloatScratchSpace coordinates(count * nDim, AT_, "coordinates");
3259 MFloatScratchSpace normalVector(count * nDim, AT_, "normalVector");
3260
3261 //-Read everything from first rank and sort it
3262 const MString varName = "geomMeasure";
3263 const MString baseFileNameSuffix = ".Netcdf";
3264 const MString baseGeometryFileName = "surface_geometryInfo_";
3265 std::ostringstream fileName;
3266 fileName << m_inputDir << baseGeometryFileName << std::to_string(m_surfaceIds[surfaceId]) << baseFileNameSuffix;
3267 ParallelIo file(fileName.str(), PIO_READ, m_mpiCommSurface[surfaceId]);
3268 if(m_domainIdSurface[surfaceId] == 0) {
3269 MFloatScratchSpace sendBuffer(noTotalCount * nDim, AT_, "sendBuffer");
3270 MFloatScratchSpace readBuffer(noTotalCount * nDim, AT_, "readBuffer");
3271 // Read surface area/length
3272 file.setOffset(noTotalCount, 0);
3273 file.readArray(readBuffer.data(), varName);
3274 for(MInt i = 0; i < noTotalCount; i++) {
3275 sendBuffer(i) = readBuffer(sortIndex[i]);
3276 }
3277 MPI_Scatterv(sendBuffer.data(), sendCount.data(), displs.data(), MPI_DOUBLE, measure.data(), count, MPI_DOUBLE,
3278 0, m_mpiCommSurface[surfaceId], AT_, "send", "recv");
3279
3280 // Read coordinates and normal vectors
3281 file.setOffset(noTotalCount * nDim, 0);
3282 file.readArray(readBuffer.data(), "centroid");
3283 for(MInt i = 0; i < noTotalCount; i++) {
3284 for(MInt j = 0; j < nDim; j++) {
3285 sendBuffer(i * nDim + j) = readBuffer(sortIndex[i] * nDim + j);
3286 }
3287 }
3288 MPI_Scatterv(sendBuffer.data(), sendCountNdim.data(), displsNdim.data(), MPI_DOUBLE, coordinates.data(),
3289 count * nDim, MPI_DOUBLE, 0, m_mpiCommSurface[surfaceId], AT_, "send", "recv");
3290 file.readArray(readBuffer.data(), "normalVector");
3291 for(MInt i = 0; i < noTotalCount; i++) {
3292 for(MInt j = 0; j < nDim; j++) {
3293 sendBuffer(i * nDim + j) = readBuffer(sortIndex[i] * nDim + j);
3294 }
3295 }
3296 MPI_Scatterv(sendBuffer.data(), sendCountNdim.data(), displsNdim.data(), MPI_DOUBLE, normalVector.data(),
3297 count * nDim, MPI_DOUBLE, 0, m_mpiCommSurface[surfaceId], AT_, "send", "recv");
3298 //-Scatter data (send)
3299 } else { // not root rank -> receive sorted data
3300 //-Scatter data (receive)
3301 MInt* dummySendBuffer = nullptr;
3302 file.setOffset(0, 0);
3303 file.readArray(dummySendBuffer, varName);
3304 MPI_Scatterv(dummySendBuffer, sendCount.data(), displs.data(), MPI_DOUBLE, measure.data(), count, MPI_DOUBLE, 0,
3305 m_mpiCommSurface[surfaceId], AT_, "send", "recv");
3306 file.readArray(dummySendBuffer, "centroid");
3307 MPI_Scatterv(dummySendBuffer, sendCountNdim.data(), displsNdim.data(), MPI_DOUBLE, coordinates.data(),
3308 count * nDim, MPI_DOUBLE, 0, m_mpiCommSurface[surfaceId], AT_, "send", "recv");
3309 file.readArray(dummySendBuffer, "normalVector");
3310 MPI_Scatterv(dummySendBuffer, sendCountNdim.data(), displsNdim.data(), MPI_DOUBLE, normalVector.data(),
3311 count * nDim, MPI_DOUBLE, 0, m_mpiCommSurface[surfaceId], AT_, "send", "recv");
3312 }
3313
3314 // Create surface elements and store its data in m_surfaceData
3315 createSurfaceElements(surfaceId, measure, coordinates, normalVector);
3316 }
3317
3318 //--Read times only on surface local rank 0 and broadcast to other ranks in this communicator
3319 {
3320 const MString varName = "time";
3321 // const MString varName = "timeStep";
3322 ScratchSpace<MFloat> timesBuffer(m_totalNoSamples, AT_, "timesBuffer");
3323 if(m_domainIdSurface[surfaceId] == 0) {
3324 MInt offsetInBuffer = 0;
3325 for(MInt fileNo = m_inputFileIndexStart; fileNo < m_inputFileIndexEnd + 1; fileNo++) {
3326 // get fileName of fileNo th sample
3327 const MString fileName = getSurfaceDataFileName(surfaceId, fileNo);
3328 ParallelIo file(fileName, PIO_READ, m_mpiCommSurface[surfaceId]);
3329
3330 std::vector<MLong> dims = file.getArrayDims(varName);
3331 const MInt samplesInFile = dims[0];
3332
3333 file.setOffset(samplesInFile, 0);
3334 file.readArray(&timesBuffer[offsetInBuffer], varName);
3335
3336 offsetInBuffer += samplesInFile;
3337 }
3338 // Check the times with those of other surfaces already loaded on this rank (before overwriting them), later
3339 // cross-check the times with all surfaces
3340 if(m_hasTimes) {
3341 for(MInt i = 0; i < noSamples(); i++) {
3342 TERMM_IF_COND(!approx(timesBuffer[m_sampleOffset + i * m_sampleStride], m_times[i], MFloatEps),
3343 "Error: local times for surfaces do not match.");
3344 }
3345 }
3346
3347 // Store times in m_times
3348 if(m_sampleStride == 1) {
3349 std::copy_n(&timesBuffer[m_sampleOffset], noSamples(), &m_times[0]);
3350 } else {
3351 for(MInt i = 0; i < noSamples(); i++) {
3352 m_times[i] = timesBuffer[m_sampleOffset + i * m_sampleStride];
3353 }
3354 }
3355 } else { // not root process
3356 for(MInt fileNo = m_inputFileIndexStart; fileNo < m_inputFileIndexEnd + 1; fileNo++) {
3357 // get fileName of fileNo th sample
3358 const MString fileName = getSurfaceDataFileName(surfaceId, fileNo);
3359 // read only dummy to ensure valid 'parallel' reading
3360 ParallelIo file(fileName, PIO_READ, m_mpiCommSurface[surfaceId]);
3361 file.setOffset(0, 0);
3362 file.readArray(&timesBuffer[0], varName);
3363 }
3364 }
3365 MPI_Bcast(&m_times[0], noSamples(), type_traits<MFloat>::mpiType(), 0, m_mpiCommSurface[surfaceId], AT_,
3366 "&m_times[0]");
3367 m_hasTimes = true;
3368 }
3369
3370 //--Read data from each file and sort it correctly into my buffers
3371 {
3372 const MInt noVars = m_inputVars.size();
3373 MInt offsetInBuffer = 0;
3374 for(MInt fileNo = m_inputFileIndexStart; fileNo < m_inputFileIndexEnd + 1; fileNo++) {
3375 //-Open the correct sample file
3376 const MString fileName = getSurfaceDataFileName(surfaceId, fileNo);
3377 ParallelIo file(fileName, PIO_READ, m_mpiCommSurface[surfaceId]);
3378
3379 //-Read variable data
3380 {
3381 // Read data
3382 const MString varName = "pointStates";
3383 file.setOffset(count, offset, 3);
3384
3385 std::vector<MLong> dims = file.getArrayDims(varName);
3386 const MInt samplesInFile = dims[1];
3387
3388 ScratchSpace<MFloat> varData(count, samplesInFile, noVars, AT_, "varData");
3389 file.readArray(varData.data(), varName);
3390
3391 // Create mapping from input variables in file to order of stored variable
3392 std::vector<MInt> varOrder;
3393 {
3394 std::map<MInt, MString> fileVars{};
3395 for(MInt varIndex = 0; varIndex < noVars; varIndex++) {
3396 MString var;
3397 file.getAttribute(&var, "var_" + std::to_string(varIndex), varName);
3398 fileVars[varIndex] = var;
3399 }
3400 for(MInt varIndex = 0; varIndex < noVars; varIndex++) {
3401 varOrder.push_back(m_inputVars[fileVars[varIndex]]);
3402 }
3403 }
3404
3405 // Store in local data
3406 for(MInt i = 0; i < count; i++) {
3407 const MInt id = surfaceOffset + i;
3408 for(MInt t = 0; t < samplesInFile && offsetInBuffer + t < noSamples(); t++) {
3409 for(MInt varIndex = 0; varIndex < noVars; varIndex++) {
3410 const MInt var = varOrder[varIndex];
3411 // TODO labels:ACA add possibility to set a strideA
3412 // TODO labels:ACA add offset, e.g. skip first X samples in files
3413 m_surfaceData.variables(id, var, offsetInBuffer + t) = varData(id, t, varIndex);
3414 }
3415 }
3416 }
3417 offsetInBuffer += samplesInFile;
3418 }
3419 }
3420 }
3421 }
3422}
3423
3424
3427template <MInt nDim>
3429 TRACE();
3430 using namespace maia::parallel_io;
3431 const MString fileName = outputDir() + m_outputFilePrefix + "out_surfaceData_"
3432 + std::to_string(m_surfaceIds[surfaceId]) + ParallelIo::fileExt();
3433 std::stringstream ss;
3434 ss << " "
3435 << "Testing: Storing surface data in " << fileName;
3436 printMessage(ss.str());
3437
3438 ParallelIo file(fileName, PIO_REPLACE, m_mpiCommSurface[surfaceId]);
3439
3440 const MInt totalCount = m_noSurfaceElementsGlobal[surfaceId];
3441 const MInt count = m_noSurfaceElements[surfaceId];
3442 const MInt offset = m_surfaceElementOffsets[surfaceId];
3443
3444 const MString measureName = (nDim == 3) ? "area" : "length";
3445 file.defineArray(PIO_FLOAT, measureName, totalCount);
3446
3447 file.defineArray(PIO_FLOAT, "time", noSamples());
3448
3449 ParallelIo::size_type dimSizesXD[] = {totalCount, nDim};
3450
3451 file.defineArray(PIO_FLOAT, "coordinates", 2, &dimSizesXD[0]);
3452 file.defineArray(PIO_FLOAT, "normalVector", 2, &dimSizesXD[0]);
3453
3454 ParallelIo::size_type dimSizesVar[] = {totalCount, noSamples()};
3455
3456 for(auto& var : m_inputVars) {
3457 const MString varName = var.first;
3458 file.defineArray(PIO_FLOAT, varName, 2, &dimSizesVar[0]);
3459 }
3460
3461 const MInt surfaceOffset = localSurfaceOffset(surfaceId);
3462
3463 // Write surface area/length
3464 file.setOffset(count, offset);
3465 file.writeArray(&m_surfaceData.surfaceArea(surfaceOffset), measureName);
3466
3467 // Write surface area/length
3468 file.setOffset(((m_domainIdSurface[surfaceId] == 0) ? noSamples() : 0), 0);
3469 file.writeArray(&m_times[0], "time");
3470
3471 // Write coordinates and normal vectors
3472 file.setOffset(count, offset, 2);
3473 file.writeArray(&m_surfaceData.surfaceCoordinates(surfaceOffset), "coordinates");
3474 file.writeArray(&m_surfaceData.surfaceNormal(surfaceOffset), "normalVector");
3475
3476 file.setOffset(count, offset, 2);
3477 ScratchSpace<MFloat> varBuffer(count, noSamples(), AT_, "varBuffer");
3478 for(auto& var : m_inputVars) {
3479 const MInt varIndex = var.second;
3480 for(MInt i = 0; i < count; i++) {
3481 for(MInt t = 0; t < noSamples(); t++) {
3482 varBuffer(i, t) = m_surfaceData.variables(surfaceOffset + i, varIndex, t);
3483 }
3484 }
3485
3486 file.writeArray(&varBuffer[0], var.first);
3487 }
3488}
3489
3490
3492template <MInt nDim>
3494 TRACE();
3495
3496 // Create all surfaces according to their specified type
3497 for(MInt sId = 0; sId < noSurfaces(); sId++) {
3512 const MInt surfaceType =
3513 Context::getSolverProperty<MInt>("surfaceType_" + std::to_string(m_surfaceIds[sId]), solverId(), AT_);
3514
3515 m_surfaceInputFileName[sId] = (surfaceType == 0) ? "generated plane" : "generated circle";
3516
3517 // Nothing to do if the surface is not present
3518 if(!hasSurface(sId)) {
3519 continue;
3520 }
3521
3522 switch(surfaceType) {
3523 case 0:
3524 generateSurfacePlane(sId);
3525 break;
3526 case 1:
3527 generateSurfaceCircle(sId);
3528 break;
3529 default:
3530 TERMM(1, "Unknown surface type: " + std::to_string(surfaceType) + "; possible values: 0 - plane.");
3531 break;
3532 }
3533 }
3534}
3535
3536
3540template <MInt nDim>
3541MInt AcaSolver<nDim>::readNoSegmentsSurfaceGeneration(const MInt sId, std::vector<MInt>& noSegments,
3542 const MInt lengthCheck) {
3552 const MString noSegmentsName = "noSegments_" + std::to_string(m_surfaceIds[sId]);
3553
3554 // If a length is given, check if it matches the property length
3555 if(lengthCheck > 0) {
3556 Context::assertPropertyLength(noSegmentsName, lengthCheck, solverId());
3557 }
3558
3559 MInt totalNoSegments = 1;
3560 const MInt propLength = Context::propertyLength(noSegmentsName);
3561 noSegments.resize(propLength);
3562
3563 // Read all property values
3564 for(MInt i = 0; i < propLength; i++) {
3565 noSegments[i] = Context::getSolverProperty<MInt>(noSegmentsName, m_solverId, AT_, i);
3566 TERMM_IF_NOT_COND(noSegments[i] >= 0, "Number of segments needs to be >= 0");
3567
3568 // Determine total number of segments by product of number of segments for each direction
3569 if(noSegments[i] > 0) {
3570 totalNoSegments *= noSegments[i];
3571 }
3572 }
3573
3574 TERMM_IF_NOT_COND(totalNoSegments > 0, "Total number of segments needs to be > 0");
3575 return totalNoSegments;
3576}
3577
3578
3580template <MInt nDim>
3582 TRACE();
3583
3584 // Weighting factor for surface averaging
3585 const MFloat weight = m_surfaceWeightingFactor[sId];
3586
3587 if constexpr(nDim == 3) {
3588 // Determine number of segments for each Cartesian direction
3589 std::vector<MInt> noSegmentsPerPlane{};
3590 // using nDim because there are all 3 directions in the properties, e.g. [0, 4, 4].
3591 MInt totalNoSeg = readNoSegmentsSurfaceGeneration(sId, noSegmentsPerPlane, nDim);
3592
3602 const MInt surfaceNormalDir =
3603 Context::getSolverProperty<MInt>("surfaceNormalDir_" + std::to_string(m_surfaceIds[sId]), m_solverId, AT_);
3604 TERMM_IF_NOT_COND(surfaceNormalDir >= 0 && surfaceNormalDir < 2 * nDim,
3605 "Invalid surface outer normal direction: " + std::to_string(surfaceNormalDir));
3606
3607 // Determine surface normal vector (axis aligned)
3608 std::vector<MFloat> surfaceNormal(nDim, 0.0);
3609 for(MInt i = 0; i < nDim; i++) {
3610 if(surfaceNormalDir == 2 * i) { // 0 == -x; 2 == -y; 4 == -z
3611 surfaceNormal[i] = -1.0;
3612 } else if(surfaceNormalDir == 2 * i + 1) { // 1 == x; 3 == y; 5 == z
3613 surfaceNormal[i] = 1.0;
3614 } else {
3615 surfaceNormal[i] = 0.0;
3616 }
3617 }
3618
3619 // Three points define the plane in 3D, make sure it is axis aligned or fix surface normal
3620 // vector
3621 std::vector<MFloat> planeCoordinates(2 * nDim, 0.0);
3622
3623 const MInt noCoords = 2 * nDim;
3634 const MString coordName = "surfaceCoordinates_" + std::to_string(m_surfaceIds[sId]);
3635 Context::assertPropertyLength(coordName, noCoords, solverId());
3636
3637 // Read coordinates
3638 for(MInt i = 0; i < noCoords; i++) {
3639 planeCoordinates[i] = Context::getSolverProperty<MFloat>(coordName, m_solverId, AT_, i);
3640 }
3641
3642 // Get length of each side
3643 const MFloat length_x = std::abs(planeCoordinates[nDim] - planeCoordinates[0]);
3644 const MFloat length_y = std::abs(planeCoordinates[nDim + 1] - planeCoordinates[1]);
3645 const MFloat length_z = std::abs(planeCoordinates[nDim + 2] - planeCoordinates[2]);
3646
3647 MFloat start_x, start_y, start_z, noSeg_x, noSeg_y, noSeg_z, stepSize_x, stepSize_y, stepSize_z;
3648 // Determine coordinates, number of Segments per direction and stepSize per direction
3649 if(approx(length_x, 0.0, MFloatEps)) {
3650 start_x = planeCoordinates[0];
3651 noSeg_x = 0;
3652 stepSize_x = 0;
3653 } else {
3654 noSeg_x =
3655 Context::getSolverProperty<MFloat>("noSegments_" + std::to_string(m_surfaceIds[sId]), m_solverId, AT_, 0);
3656 stepSize_x = length_x / noSeg_x;
3657 start_x = planeCoordinates[0] + stepSize_x / 2.0;
3658 }
3659
3660 if(approx(length_y, 0.0, MFloatEps)) {
3661 start_y = planeCoordinates[1];
3662 noSeg_y = 0;
3663 stepSize_y = 0;
3664 } else {
3665 noSeg_y =
3666 Context::getSolverProperty<MFloat>("noSegments_" + std::to_string(m_surfaceIds[sId]), m_solverId, AT_, 1);
3667 stepSize_y = length_y / noSeg_y;
3668 start_y = planeCoordinates[1] + stepSize_y / 2.0;
3669 }
3670
3671 if(approx(length_z, 0.0, MFloatEps)) {
3672 start_z = planeCoordinates[2];
3673 noSeg_z = 0;
3674 stepSize_z = 0;
3675 } else {
3676 noSeg_z =
3677 Context::getSolverProperty<MFloat>("noSegments_" + std::to_string(m_surfaceIds[sId]), m_solverId, AT_, 2);
3678 stepSize_z = length_z / noSeg_z;
3679 start_z = planeCoordinates[2] + stepSize_z / 2.0;
3680 }
3681
3682 // Check for axis aligned surface
3683 TERMM_IF_NOT_COND(
3684 (approx(length_x, 0.0, MFloatEps) || approx(length_y, 0.0, MFloatEps) || approx(length_z, 0.0, MFloatEps)),
3685 "Plane not axis aligned.");
3686
3687 std::vector<MFloat> coordinates_buff(nDim * totalNoSeg, 0.0);
3688 MFloat delta_segment = 0.0;
3689 // Determine segment area and save coordinates in data buffer
3690 if(approx(stepSize_x, 0.0, MFloatEps)) {
3691 delta_segment = stepSize_y * stepSize_z;
3692
3693 for(MInt j = 0; j < noSeg_z; j++) {
3694 for(MInt i = 0; i < noSeg_y; i++) {
3695 coordinates_buff[j * 3 * noSeg_y + i * 3] = start_x;
3696 coordinates_buff[j * 3 * noSeg_y + i * 3 + 1] = start_y + i * stepSize_y;
3697 coordinates_buff[j * 3 * noSeg_y + i * 3 + 2] = start_z + j * stepSize_z;
3698 }
3699 }
3700 } else if(approx(stepSize_y, 0.0, MFloatEps)) {
3701 delta_segment = stepSize_x * stepSize_z;
3702
3703 for(MInt j = 0; j < noSeg_z; j++) {
3704 for(MInt i = 0; i < noSeg_x; i++) {
3705 coordinates_buff[j * 3 * noSeg_x + i * 3] = start_x + i * stepSize_x;
3706 coordinates_buff[j * 3 * noSeg_x + i * 3 + 1] = start_y;
3707 coordinates_buff[j * 3 * noSeg_x + i * 3 + 2] = start_z + j * stepSize_z;
3708 }
3709 }
3710
3711 } else if(approx(stepSize_z, 0.0, MFloatEps)) {
3712 delta_segment = stepSize_x * stepSize_y;
3713
3714 for(MInt j = 0; j < noSeg_x; j++) {
3715 for(MInt i = 0; i < noSeg_y; i++) {
3716 coordinates_buff[j * 3 * noSeg_y + i * 3] = start_x + j * stepSize_x;
3717 coordinates_buff[j * 3 * noSeg_y + i * 3 + 1] = start_y + i * stepSize_y;
3718 coordinates_buff[j * 3 * noSeg_y + i * 3 + 2] = start_z;
3719 }
3720 }
3721 }
3722
3723 // Surface Offset in the case that one processor needs to compute points on different surfaces.
3724 const MInt surfaceOffset = localSurfaceOffset(sId);
3725
3726 // Loop over all local elements
3727 for(MInt i = 0; i < m_noSurfaceElements[sId]; i++) {
3728 const MInt id = surfaceOffset + i;
3729 // Add a new surface element
3730 m_surfaceData.append();
3731 // Check that the total size of m_surfaceData is correct
3732 TERMM_IF_NOT_COND(m_surfaceData.size() == id + 1, "m_surfaceData size mismatch.");
3733
3734 // Id of segment on the surface. In case processor starts computing points somewhere on the
3735 // surface.
3736 const MInt segmentId = i + m_surfaceElementOffsets[sId];
3737 // Check for valid segment id
3738 TERMM_IF_NOT_COND(segmentId >= 0 && segmentId < totalNoSeg, "invalid segment id");
3739
3740 // Read out coordinates from buffer
3741 const MFloat xcenter = coordinates_buff[3 * segmentId];
3742 const MFloat ycenter = coordinates_buff[3 * segmentId + 1];
3743 const MFloat zcenter = coordinates_buff[3 * segmentId + 2];
3744
3745 // Store segment center coordinates
3746 MFloat* surfCoordinates = &m_surfaceData.surfaceCoordinates(id);
3747 surfCoordinates[0] = xcenter;
3748 surfCoordinates[1] = ycenter;
3749 surfCoordinates[2] = zcenter;
3750
3751 // Store segment area (weighted with surface averaging factor)
3752 m_surfaceData.surfaceArea(id) = weight * delta_segment;
3753
3754 // Store segment normal vector
3755 MFloat* surfNormal = &m_surfaceData.surfaceNormal(id);
3756 surfNormal[0] = surfaceNormal[0];
3757 surfNormal[1] = surfaceNormal[1];
3758 surfNormal[2] = surfaceNormal[2];
3759 }
3760 } else if constexpr(nDim == 2) {
3761 // Determine number of segments for each Cartesian direction
3762 std::vector<MInt> noSegmentsPerDir{};
3763 readNoSegmentsSurfaceGeneration(sId, noSegmentsPerDir, nDim - 1);
3764
3765 // Get surface outer normal direction (0:-x; 1:x; 2:-y; 3:y; 4:-z; 5:z)
3766 const MInt surfaceNormalDir =
3767 Context::getSolverProperty<MInt>("surfaceNormalDir_" + std::to_string(m_surfaceIds[sId]), m_solverId, AT_);
3768 TERMM_IF_NOT_COND(surfaceNormalDir >= 0 && surfaceNormalDir < 2 * nDim,
3769 "Invalid surface outer normal direction: " + std::to_string(surfaceNormalDir));
3770
3771 // Determine surface normal vector (axis aligned)
3772 std::vector<MFloat> surfaceNormal(nDim, 0.0);
3773 for(MInt i = 0; i < nDim; i++) {
3774 if(surfaceNormalDir == 2 * i) { // 0 == -x; 2 == -y; 4 == -z
3775 surfaceNormal[i] = -1.0;
3776 } else if(surfaceNormalDir == 2 * i + 1) { // 1 == x; 3 == y; 5 == z
3777 surfaceNormal[i] = 1.0;
3778 } else {
3779 surfaceNormal[i] = 0.0;
3780 }
3781 }
3782
3783 // Two points define the plane in 2D, make sure it is axis aligned or fix surface normal vector
3784 std::vector<MFloat> planeCoordinates(2 * nDim, 0.0);
3785
3786 const MInt noCoords = 2 * nDim;
3787 const MString coordName = "surfaceCoordinates_" + std::to_string(m_surfaceIds[sId]);
3788 Context::assertPropertyLength(coordName, noCoords, solverId());
3789
3790 // Read coordinates
3791 for(MInt i = 0; i < noCoords; i++) {
3792 planeCoordinates[i] = Context::getSolverProperty<MFloat>(coordName, m_solverId, AT_, i);
3793 }
3794
3795 const MFloat noSegmentsInv = 1.0 / (noSegmentsPerDir[0]);
3796 const MFloat Delta_x = (planeCoordinates[nDim] - planeCoordinates[0]);
3797 const MFloat Delta_y = (planeCoordinates[nDim + 1] - planeCoordinates[1]);
3798
3799 // Check for axis aligned surface
3800 TERMM_IF_NOT_COND(approx(Delta_x, 0.0, MFloatEps) || approx(Delta_y, 0.0, MFloatEps), "Plane not axis aligned.");
3801
3802 const MInt surfaceOffset = localSurfaceOffset(sId);
3803
3804 // Loop over local number of surface elememts
3805 for(MInt i = 0; i < m_noSurfaceElements[sId]; i++) {
3806 const MInt id = surfaceOffset + i;
3807 // Add a new surface element
3808 m_surfaceData.append();
3809 // Check that the total size of m_surfaceData is correct
3810 TERMM_IF_NOT_COND(m_surfaceData.size() == id + 1, "m_surfaceData size mismatch.");
3811
3812 // Id of segment on the surface
3813 const MInt segmentId = i + m_surfaceElementOffsets[sId];
3814 // Check for valid segment id
3815 TERMM_IF_NOT_COND(segmentId >= 0 && segmentId < noSegmentsPerDir[0], "invalid segment id");
3816
3817 // First segment point
3818 const MFloat p1_x = planeCoordinates[0] + segmentId * noSegmentsInv * Delta_x;
3819 const MFloat p1_y = planeCoordinates[1] + segmentId * noSegmentsInv * Delta_y;
3820
3821 // Second segment point
3822 const MFloat p2_x = planeCoordinates[0] + (segmentId + 1) * noSegmentsInv * Delta_x;
3823 const MFloat p2_y = planeCoordinates[1] + (segmentId + 1) * noSegmentsInv * Delta_y;
3824
3825 // Segment center
3826 const MFloat xcenter = 0.5 * (p2_x + p1_x);
3827 const MFloat ycenter = 0.5 * (p2_y + p1_y);
3828
3829 // Determine segment length
3830 const MFloat dx = (p2_x - p1_x);
3831 const MFloat dy = (p2_y - p1_y);
3832 const MFloat delta_segment = sqrt(dx * dx + dy * dy);
3833
3834 // Store segment center coordinates
3835 MFloat* surfCoordinates = &m_surfaceData.surfaceCoordinates(id);
3836 surfCoordinates[0] = xcenter;
3837 surfCoordinates[1] = ycenter;
3838
3839 // Store segment area (weighted with surface averaging factor)
3840 m_surfaceData.surfaceArea(id) = weight * delta_segment;
3841
3842 // Store segment normal vector
3843 MFloat* surfNormal = &m_surfaceData.surfaceNormal(id);
3844 surfNormal[0] = surfaceNormal[0];
3845 surfNormal[1] = surfaceNormal[1];
3846 }
3847 }
3848}
3849
3850
3852template <MInt nDim>
3854 TRACE();
3855
3856 // Weighting factor for surface averaging
3857 const MFloat weight = m_surfaceWeightingFactor[sId];
3858
3859 if constexpr(nDim != 2) {
3860 TERMM(1, "Only implemented in 2D.");
3861 }
3862
3863 // TODO labels:ACA Fix this
3864 TERMM(1, "The 2D implementation is not tested and seems not to work properly.");
3865
3866 // Creating Circle with radius and number of Elements
3867 MInt totalNoSurfaceElements = 100;
3868 totalNoSurfaceElements = Context::getSolverProperty<MInt>("noSegments_" + std::to_string(m_surfaceIds[sId]),
3869 m_solverId, AT_, &totalNoSurfaceElements);
3870
3871 MFloat radius = 1.0;
3872 radius = Context::getSolverProperty<MFloat>("radius", m_solverId, AT_, &radius);
3873
3874 const MFloat step_rad = 2 * PI / static_cast<MFloat>(totalNoSurfaceElements);
3875
3876 std::vector<MFloat> surfaceNormal(2, 0.0);
3877 const MInt surfaceOffset = localSurfaceOffset(sId);
3878 for(MInt i = 0; i < m_noSurfaceElements[sId]; i++) {
3879 const MInt id = surfaceOffset + i;
3880 // Add a new surface element
3881 m_surfaceData.append();
3882 // Check that the total size of m_surfaceData is correct
3883 TERMM_IF_NOT_COND(m_surfaceData.size() == id + 1, "m_surfaceData size mismatch.");
3884
3885 // First surface point
3886 const MFloat p1_x = radius * cos(i * step_rad);
3887 const MFloat p1_y = radius * sin(i * step_rad);
3888
3889 // Second surface point
3890 const MFloat p2_x = radius * cos((i + 1) * step_rad);
3891 const MFloat p2_y = radius * sin((i + 1) * step_rad);
3892
3893 // Segment center
3894 const MFloat xcenter = 0.5 * (p2_x + p1_x);
3895 const MFloat ycenter = 0.5 * (p2_y + p1_y);
3896
3897 // Determine segment length
3898 const MFloat dx = (p2_x - p1_x);
3899 const MFloat dy = (p2_y - p1_y);
3900 const MFloat delta_segment = sqrt(dx * dx + dy * dy);
3901
3902 // Store segment center coordinates
3903 MFloat* surfCoordinates = &m_surfaceData.surfaceCoordinates(id);
3904 surfCoordinates[0] = xcenter;
3905 surfCoordinates[1] = ycenter;
3906
3907 // Store segment area (weighted with surface averaging factor)
3908 m_surfaceData.surfaceArea(i) = weight * delta_segment;
3909
3910 // Calculate surface normal vector. Simply the gradient and normed
3911 surfaceNormal[0] = 2 * xcenter / (sqrt(4 * xcenter * xcenter + 4 * ycenter * ycenter));
3912 surfaceNormal[1] = 2 * ycenter / (sqrt(4 * xcenter * xcenter + 4 * ycenter * ycenter));
3913
3914 // Store segment normal vector
3915 MFloat* surfNormal = &m_surfaceData.surfaceNormal(id);
3916 surfNormal[0] = surfaceNormal[0];
3917 surfNormal[1] = surfaceNormal[1];
3918 }
3919}
3920
3921
3923template <MInt nDim>
3925 TRACE();
3926
3927 const MInt noVars = m_surfaceData.noVars();
3928 TERMM_IF_NOT_COND(noVars == m_noVariables, "number of variables mismatch");
3929
3930 // TODO labels:ACA Read in analytic or semi-analytic
3931 const MBool analytic = true;
3932 /* analytic = Context::getSolverProperty<MBool>("analyticSourceGeneration", m_solverId, AT_,
3933 * &analytic); */
3934
3935 // Initialize every variable with zeros
3936 std::fill_n(&m_surfaceData.variables(0, 0, 0), totalNoSurfaceElements() * noSamples() * noVars, 0.0);
3937
3938 TERMM_IF_NOT_COND(m_dt > 0.0, "Invalid constant time step size: " + std::to_string(m_dt));
3939 // Initialize times (constant timestep size)
3940
3941 for(MInt t = 0; t < noSamples(); t++) {
3942 m_times[t] = m_dt * t;
3943 }
3944
3945 // TODO labels:ACA,toenhance add possibility for superposition of multiple sources with different source parameters
3946 const MInt noSources = 1;
3947
3948 for(MInt sourceId = 0; sourceId < noSources; sourceId++) {
3949 // TODO labels:ACA,toenhance change structure (performance)?
3950 // Iterate over all surface segments and all observers.
3951 for(MInt segmentId = 0; segmentId < totalNoSurfaceElements(); segmentId++) {
3952 const MFloat* coord = &m_surfaceData.surfaceCoordinates(segmentId);
3953
3954 SourceVars sourceVars;
3955 if(m_acousticMethod == FWH_METHOD) {
3956 sourceVars.p = &m_surfaceData.variables(segmentId, FWH::P);
3957 sourceVars.u = &m_surfaceData.variables(segmentId, FWH::U);
3958 sourceVars.v = &m_surfaceData.variables(segmentId, FWH::V);
3959 sourceVars.w = &m_surfaceData.variables(segmentId, FWH::W);
3960 sourceVars.rho = &m_surfaceData.variables(segmentId, FWH::RHO);
3961 } else {
3962 sourceVars.p = &m_surfaceData.variables(segmentId, FWH_APE::PP);
3963 sourceVars.u = &m_surfaceData.variables(segmentId, FWH_APE::UP);
3964 sourceVars.v = &m_surfaceData.variables(segmentId, FWH_APE::VP);
3965 sourceVars.w = &m_surfaceData.variables(segmentId, FWH_APE::WP);
3966 }
3967
3968 if(analytic) {
3969 // Full analytic formulation
3970 if constexpr(nDim == 2) {
3971 switch(m_sourceType) {
3972 case 0: {
3973 genMonopoleAnalytic2D(coord, m_sourceParameters, sourceVars);
3974 break;
3975 }
3976 case 1: {
3977 genDipoleAnalytic2D(coord, m_sourceParameters, sourceVars);
3978 break;
3979 }
3980 case 2: {
3981 genQuadrupoleAnalytic2D(coord, m_sourceParameters, sourceVars);
3982 break;
3983 }
3984 case 3: {
3985 genVortexConvectionAnalytic2D(coord, m_sourceParameters, sourceVars);
3986 break;
3987 }
3988 default: {
3989 TERMM(1, "source type not implemented");
3990 break;
3991 }
3992 }
3993 } else {
3994 switch(m_sourceType) {
3995 case 0: {
3996 genMonopoleAnalytic3D(coord, m_sourceParameters, sourceVars);
3997 break;
3998 }
3999 case 1: {
4000 genDipoleAnalytic3D(coord, m_sourceParameters, sourceVars);
4001 break;
4002 }
4003 case 2: {
4004 genQuadrupoleAnalytic3D(coord, m_sourceParameters, sourceVars);
4005 break;
4006 }
4007
4008 default: {
4009 TERMM(1, "source type not implemented");
4010 break;
4011 }
4012 }
4013 }
4014 } else {
4015 TERMM(1, "Error: semi-analytic source generation not implemented.");
4016 // TODO labels:ACA Semi-analytic formulation (numeric differentiation of acoustic potential)
4017 // 1. generate the acoustic potential (without derivatives) for the current
4018 // segment and a surrounding stencil of points
4019 // 2. use finite differences to:
4020 // 2.1 compute the derivatives to get the correct acoustic potential (eg. dipole/quadrupole)
4021 // 2.2 compute the derivatives to get the variables p', u', ...
4022 /* if constexpr(nDim == 2) { */
4023 /* genSurfaceDataNumeric2D(segmentId, m_sourceParameters); */
4024 /* } else { */
4025 /* genSurfaceDataNumeric3D(segmentId, m_sourceParameters); */
4026 /* } */
4027 }
4028 } // end of segmentId for loop
4029 } // end of sourceType loop
4030}
4031
4032
4034template <MInt nDim>
4036 MFloat x = coord[0];
4037 MFloat y = coord[1];
4038 transformCoordinatesToFlowDirection2D(&x, &y);
4039
4040 const MFloat omega = param.omega;
4041 const MFloat c0 = 1.0; // by definition
4042 const MFloat rho0 = param.rho0;
4043 const MFloat p0 = rho0 * c0 * c0 / m_gamma;
4044 const MFloat beta = param.beta;
4045 const MFloat amplitude = param.amplitude;
4046 const MFloat u0 = param.Ma * c0;
4047 const MFloat mach = param.Ma;
4048
4049 // Precompute time constant values/factors
4050 const MFloat k = omega / c0;
4051
4052 const MFloat d = sqrt(x * x + beta * beta * y * y);
4053 const MFloat xfd = x / d;
4054 const MFloat yfd = y / d;
4055 const MFloat beta2 = (beta * beta);
4056 const MFloat fbeta2 = 1 / beta2;
4057 const MFloat kfbeta2 = k / (beta * beta);
4058 const MFloat amplwf4beta = amplitude * omega / (4.0 * beta);
4059 const MFloat amplkf4beta3 = amplitude * k / (4.0 * beta * beta2);
4060 const MFloat argHankel = kfbeta2 * d;
4061 const MFloat J0 = j0(argHankel);
4062 const MFloat J1 = j1(argHankel);
4063 const MFloat Y0 = y0(argHankel);
4064 const MFloat Y1 = y1(argHankel);
4065
4066 for(MInt sample = 0; sample < noSamples(); sample++) {
4067 // Note: adapted from acoupot3d/acou_pot.cpp::calc_p/ux/uy/uz_analytic()
4068 const MFloat time = sample * m_dt;
4069 const MFloat arg = omega * time + mach * k * x * fbeta2;
4070
4071 const MFloat cosarg = cos(arg);
4072 const MFloat sinarg = sin(arg);
4073
4074 const MFloat dPhidt = -1.0 * amplwf4beta * (cosarg * J0 + sinarg * Y0);
4075 const MFloat dPhidx =
4076 -1.0 * mach * amplkf4beta3 * (cosarg * J0 + sinarg * Y0) + amplkf4beta3 * xfd * (sinarg * J1 - cosarg * Y1);
4077 const MFloat dPhidy = amplkf4beta3 * yfd * beta2 * (-1.0 * cosarg * Y1 + sinarg * J1);
4078 const MFloat pp = -1.0 * rho0 * (dPhidt + u0 * dPhidx);
4079
4080 if(param.perturbed) {
4081 vars.u[sample] = dPhidx;
4082 vars.v[sample] = dPhidy;
4083 vars.p[sample] = pp;
4084 } else {
4085 vars.u[sample] = dPhidx + u0;
4086 vars.v[sample] = dPhidy;
4087 vars.p[sample] = pp + p0;
4088 vars.rho[sample] = rho0 + pp / (c0 * c0);
4089 }
4090 transformBackToOriginalCoordinate2D(&vars.u[sample], &vars.v[sample]);
4091 }
4092}
4093
4094
4096template <MInt nDim>
4098 MFloat x = coord[0];
4099 MFloat y = coord[1];
4100 MFloat z = coord[2];
4101 transformCoordinatesToFlowDirection3D(&x, &y, &z);
4102
4103 const MFloat omega = param.omega;
4104 const MFloat c0 = 1.0; // by definition
4105 const MFloat rho0 = param.rho0;
4106 const MFloat p0 = rho0 * c0 * c0 / m_gamma;
4107 const MFloat beta = param.beta;
4108 const MFloat amplitude = param.amplitude;
4109 const MFloat u0 = param.Ma * c0;
4110 const MFloat mach = param.Ma;
4111
4112 // Precompute time constant values/factors
4113 // Wavenumber k needs to be dimensionless
4114 MFloat k = omega / c0;
4115
4116 const MFloat d = sqrt(x * x + beta * beta * (y * y + z * z));
4117 const MFloat xfd = x / d;
4118 const MFloat xfd2 = x / (d * d);
4119 const MFloat yfd = y / d;
4120 const MFloat yfd2 = y / (d * d);
4121 const MFloat zfd = z / d;
4122 const MFloat zfd2 = z / (d * d);
4123 const MFloat ampf4pid = amplitude / (4.0 * PI * d);
4124 const MFloat beta2 = (beta * beta);
4125 const MFloat fbeta2 = 1.0 / (beta * beta);
4126
4127 for(MInt sample = 0; sample < noSamples(); sample++) {
4128 // Note: adapted from acoupot3d/acou_pot.cpp::calc_p/ux/uy/uz_analytic()
4129 const MFloat time = sample * m_dt;
4130 const MFloat arg = omega * time - k * (d - mach * x) * fbeta2;
4131
4132 const MFloat cosarg = cos(arg);
4133 const MFloat sinarg = sin(arg);
4134
4135 const MFloat dPhidt = -1.0 * ampf4pid * omega * sinarg;
4136 const MFloat dPhidx = -1.0 * ampf4pid * xfd2 * cosarg + ampf4pid * k * fbeta2 * sinarg * (xfd - mach);
4137 const MFloat dPhidy = -1.0 * ampf4pid * beta2 * yfd2 * cosarg + ampf4pid * k * yfd * sinarg;
4138 const MFloat dPhidz = -1.0 * ampf4pid * beta2 * zfd2 * cosarg + ampf4pid * k * zfd * sinarg;
4139 const MFloat pp = -1.0 * rho0 * (dPhidt + u0 * dPhidx);
4140
4141 if(param.perturbed) {
4142 vars.u[sample] = dPhidx;
4143 vars.v[sample] = dPhidy;
4144 vars.w[sample] = dPhidz;
4145 vars.p[sample] = pp;
4146 } else {
4147 vars.u[sample] = dPhidx + u0;
4148 vars.v[sample] = dPhidy;
4149 vars.w[sample] = dPhidz;
4150 vars.p[sample] = pp + p0;
4151 vars.rho[sample] = rho0 + pp / (c0 * c0);
4152 }
4153 transformBackToOriginalCoordinate3D(&vars.u[sample], &vars.v[sample], &vars.w[sample]);
4154 }
4155}
4156
4157
4159template <MInt nDim>
4161 MFloat x = coord[0];
4162 MFloat y = coord[1];
4163 transformCoordinatesToFlowDirection2D(&x, &y);
4164
4165 const MFloat omega = param.omega;
4166 const MFloat c0 = 1.0; // by definition
4167 const MFloat rho0 = param.rho0;
4168 const MFloat p0 = rho0 * c0 * c0 / m_gamma;
4169 const MFloat beta = param.beta;
4170 const MFloat G = param.amplitude;
4171 const MFloat u0 = param.Ma * c0;
4172 const MFloat mach = param.Ma;
4173
4174 // Precompute time constant values/factors
4175 const MFloat beta2 = (beta * beta);
4176 const MFloat fbeta2 = 1 / beta2;
4177
4178 MFloat k = omega / c0;
4179
4180 const MFloat d = sqrt(x * x + beta2 * y * y);
4181 const MFloat dargdx = mach * k * fbeta2;
4182 const MFloat argHankel = k * d * fbeta2;
4183 const MFloat dHdx = k * x * fbeta2 / d;
4184 const MFloat dHdxdx = k * y * y / (d * d * d);
4185 const MFloat dHdxdy = -1.0 * k * x * y / (d * d * d);
4186 const MFloat dHdy = k * y / d;
4187 const MFloat C1 = -1.0 * mach * G * k / (4.0 * beta2 * beta);
4188 const MFloat C2 = G / (4.0 * beta);
4189
4190 const MFloat J0 = j0(argHankel);
4191 const MFloat J1 = j1(argHankel);
4192 const MFloat dJ1 = J0 - (1 / argHankel) * J1;
4193
4194 const MFloat Y0 = y0(argHankel);
4195 const MFloat Y1 = y1(argHankel);
4196 const MFloat dY1 = Y0 - (1 / argHankel) * Y1;
4197
4198 for(MInt sample = 0; sample < noSamples(); sample++) {
4199 const MFloat time = sample * m_dt;
4200 const MFloat arg = omega * time + mach * k * x * fbeta2;
4201
4202 const MFloat cosarg = cos(arg);
4203 const MFloat sinarg = sin(arg);
4204
4205 const MFloat dPhidt =
4206 C1 * omega * (-1.0 * sinarg * J0 + cosarg * Y0) + C2 * dHdx * omega * (cosarg * J1 + sinarg * Y1);
4207 const MFloat dPhidx = -1.0 * C1 * (dargdx * (sinarg * J0 - cosarg * Y0) + dHdx * (cosarg * J1 + sinarg * Y1))
4208 + C2
4209 * (dHdxdx * (sinarg * J1 - cosarg * Y1) + dHdx * dargdx * (cosarg * J1 + sinarg * Y1)
4210 + dHdx * dHdx * (sinarg * dJ1 - cosarg * dY1));
4211
4212 const MFloat dPhidy = -1.0 * C1 * dHdy * (cosarg * J1 + sinarg * Y1)
4213 + C2 * (dHdxdy * (sinarg * J1 - cosarg * Y1) + dHdx * dHdy * (sinarg * dJ1 - cosarg * dY1));
4214 const MFloat pp = -1.0 * rho0 * (dPhidt + u0 * dPhidx);
4215
4216 if(param.perturbed) {
4217 vars.u[sample] = dPhidx;
4218 vars.v[sample] = dPhidy;
4219 vars.p[sample] = pp;
4220 } else {
4221 vars.u[sample] = dPhidx + u0;
4222 vars.v[sample] = dPhidy;
4223 vars.p[sample] = pp + p0;
4224 vars.rho[sample] = rho0 + pp / (c0 * c0);
4225 }
4226 transformBackToOriginalCoordinate2D(&vars.u[sample], &vars.v[sample]);
4227 }
4228}
4229
4230
4232template <MInt nDim>
4234 MFloat x = coord[0];
4235 MFloat y = coord[1];
4236 MFloat z = coord[2];
4237 transformCoordinatesToFlowDirection3D(&x, &y, &z);
4238
4239 const MFloat omega = param.omega;
4240 const MFloat c0 = 1.0; // by definition
4241 const MFloat rho0 = param.rho0;
4242 const MFloat p0 = rho0 * c0 * c0 / m_gamma;
4243 const MFloat beta = param.beta;
4244 const MFloat amplitude = param.amplitude;
4245 const MFloat u0 = param.Ma * c0;
4246 const MFloat mach = param.Ma;
4247
4248 MFloat k = omega / c0;
4249
4250 const MFloat beta2 = beta * beta;
4251 const MFloat fbeta2 = 1 / beta2;
4252 const MFloat d = sqrt(x * x + beta * beta * (y * y + z * z));
4253 const MFloat d2 = d * d;
4254 const MFloat fd2 = 1 / d2;
4255 const MFloat fd3 = 1 / (d2 * d);
4256 const MFloat C1 = amplitude / (4 * PI);
4257 const MFloat C2 = k * fbeta2;
4258
4259 const MFloat P1 = fd3 - 3 * x * x * fd2 * fd3;
4260 const MFloat P2 = 1 * fd2 - 2 * x * x * fd2 * fd2;
4261 const MFloat P3 = -1.0 * x * fd3;
4262
4263 const MFloat dargdx = -1.0 * C2 * (x / d - mach);
4264 const MFloat dargdy = -1.0 * k * y / d;
4265 const MFloat dargdz = -1.0 * k * z / d;
4266
4267 for(MInt sample = 0; sample < noSamples(); sample++) {
4268 // Note: adapted from acoupot3d/acou_pot.cpp::calc_p/ux/uy/uz_analytic()
4269 const MFloat time = sample * m_dt;
4270 const MFloat arg = omega * time - k * (d - mach * x) * fbeta2;
4271
4272 const MFloat cosarg = cos(arg);
4273 const MFloat sinarg = sin(arg);
4274
4275 const MFloat dPhidt =
4276 C1 * omega * fd3 * x * sinarg + C1 * omega * fd2 * x * C2 * cosarg - C1 * omega * C2 * (mach / d) * cosarg;
4277 const MFloat dPhidx = -1.0 * C1 * P1 * cosarg + C1 * x * fd3 * dargdx * sinarg + C1 * P2 * C2 * sinarg
4278 + C1 * x * fd2 * C2 * dargdx * cosarg - C1 * C2 * P3 * mach * sinarg
4279 - C1 * C2 * (mach / d) * dargdx * cosarg;
4280 const MFloat dPhidy = C1 * x * 3 * y * beta2 * fd3 * fd2 * cosarg + C1 * x * fd3 * dargdy * sinarg
4281 - C1 * x * C2 * 2 * y * beta2 * fd2 * fd2 * sinarg + C1 * x * C2 * fd2 * dargdy * cosarg
4282 + C1 * C2 * y * beta2 * mach * fd3 * sinarg - C1 * C2 * (mach / d) * dargdy * cosarg;
4283 const MFloat dPhidz = C1 * x * 3 * z * beta2 * fd3 * fd2 * cosarg + C1 * x * fd3 * dargdz * sinarg
4284 - C1 * x * C2 * 2 * z * beta2 * fd2 * fd2 * sinarg + C1 * x * C2 * fd2 * dargdz * cosarg
4285 + C1 * C2 * z * beta2 * mach * fd3 * sinarg - C1 * C2 * (mach / d) * dargdz * cosarg;
4286 const MFloat pp = -1.0 * rho0 * (dPhidt + u0 * dPhidx);
4287
4288 if(param.perturbed) {
4289 vars.u[sample] = dPhidx;
4290 vars.v[sample] = dPhidy;
4291 vars.w[sample] = dPhidz;
4292 vars.p[sample] = pp;
4293 } else {
4294 vars.u[sample] = dPhidx + u0;
4295 vars.v[sample] = dPhidy;
4296 vars.w[sample] = dPhidz;
4297 vars.p[sample] = pp + p0;
4298 vars.rho[sample] = rho0 + pp / (c0 * c0);
4299 }
4300 transformBackToOriginalCoordinate3D(&vars.u[sample], &vars.v[sample], &vars.w[sample]);
4301 }
4302}
4303
4304
4306template <MInt nDim>
4308 MFloat x = coord[0];
4309 MFloat y = coord[1];
4310 transformCoordinatesToFlowDirection2D(&x, &y);
4311
4312 const MFloat omega = param.omega;
4313 const MFloat c0 = 1.0; // by definition
4314 const MFloat rho0 = param.rho0;
4315 const MFloat p0 = rho0 * c0 * c0 / m_gamma;
4316 const MFloat beta = param.beta;
4317 const MFloat amplitude = param.amplitude;
4318 const MFloat u0 = param.Ma * c0;
4319 const MFloat mach = param.Ma;
4320
4321 MFloat k = omega / c0;
4322
4323 const MFloat beta2 = beta * beta;
4324 const MFloat fbeta2 = 1 / beta2;
4325 const MFloat d = sqrt(x * x + beta2 * y * y);
4326 const MFloat dargdx = mach * k * fbeta2;
4327 const MFloat argHankel = k * d * fbeta2;
4328 const MFloat K1 = -1.0 * mach * amplitude * k / (4.0 * beta2 * beta);
4329 const MFloat K2 = amplitude / (4.0 * beta);
4330
4331 const MFloat dHdx = k * x * fbeta2 / d;
4332 const MFloat dHdy = k * y / d;
4333
4334 const MFloat dHdxdx = k * y * y / (d * d * d);
4335 const MFloat dHdxdy = -1.0 * k * x * y / (d * d * d);
4336 const MFloat dHdydx = dHdxdy;
4337 const MFloat dHdydy = k * x * x / (d * d * d);
4338
4339 const MFloat dHdxdydx = k * y * (2 * x * x - beta2 * y * y) / (d * d * d * d * d);
4340 const MFloat dHdxdydy = k * x * (2 * beta2 * y * y - x * x) / (d * d * d * d * d);
4341
4342 const MFloat dHdxfH = x / (d * d);
4343 const MFloat dHdxfHdx = -1.0 * (x * x - beta2 * y * y) / (d * d * d * d);
4344 const MFloat dHdxfHdy = -1.0 * 2 * beta2 * x * y / (d * d * d * d);
4345
4346
4347 const MFloat J0 = j0(argHankel);
4348 const MFloat J1 = j1(argHankel);
4349 const MFloat dJ1 = J0 - (1 / argHankel) * J1;
4350
4351 const MFloat Y0 = y0(argHankel);
4352 const MFloat Y1 = y1(argHankel);
4353 const MFloat dY1 = Y0 - (1 / argHankel) * Y1;
4354
4355 for(MInt sample = 0; sample < noSamples(); sample++) {
4356 const MFloat time = sample * m_dt;
4357 const MFloat arg = omega * time + mach * k * x * fbeta2;
4358
4359 const MFloat cosarg = cos(arg);
4360 const MFloat sinarg = sin(arg);
4361
4362 const MFloat dPhidt = K1 * dHdy * sinarg * omega * J1 - K1 * dHdy * cosarg * omega * Y1
4363 + K2 * dHdxdy * cosarg * omega * J1 + K2 * dHdxdy * sinarg * omega * Y1
4364 + K2 * dHdx * dHdy * cosarg * omega * J0 - K2 * dHdxfH * dHdy * cosarg * omega * J1
4365 + K2 * dHdx * dHdy * sinarg * omega * Y0 - K2 * dHdxfH * dHdy * sinarg * omega * Y1;
4366
4367 const MFloat dPhidx =
4368 -1.0 * K1 * dHdydx * cosarg * J1 + K1 * dHdy * sinarg * dargdx * J1 - K1 * dHdy * cosarg * dJ1 * dHdx
4369 - K1 * dHdydx * sinarg * Y1 - K1 * dHdy * cosarg * dargdx * Y1 - K1 * dHdy * sinarg * dY1 * dHdx
4370 + K2 * dHdxdydx * sinarg * J1 + K2 * dHdxdy * cosarg * dargdx * J1 + K2 * dHdxdy * sinarg * dJ1 * dHdx
4371 - K2 * dHdxdydx * cosarg * Y1 + K2 * dHdxdy * sinarg * dargdx * Y1 - K2 * dHdxdy * cosarg * dY1 * dHdx
4372 + K2 * dHdxdx * dHdy * sinarg * J0 + K2 * dHdx * dHdydx * sinarg * J0 + K2 * dHdx * dHdy * cosarg * dargdx * J0
4373 - K2 * dHdx * dHdy * sinarg * J1 * dHdx - K2 * dHdxfHdx * dHdy * sinarg * J1
4374 - K2 * dHdxfH * dHdydx * sinarg * J1 - K2 * dHdxfH * dHdy * cosarg * dargdx * J1
4375 - K2 * dHdxfH * dHdy * sinarg * dJ1 * dHdx - K2 * dHdxdx * dHdy * cosarg * Y0 - K2 * dHdx * dHdydx * cosarg * Y0
4376 + K2 * dHdx * dHdy * sinarg * dargdx * Y0 + K2 * dHdx * dHdy * cosarg * Y1 * dHdx
4377 + K2 * dHdxfHdx * dHdy * cosarg * Y1 + K2 * dHdxfH * dHdydx * cosarg * Y1
4378 - K2 * dHdxfH * dHdy * sinarg * dargdx * Y1 + K2 * dHdxfH * dHdy * cosarg * dY1 * dHdx;
4379
4380 const MFloat dPhidy =
4381 -1.0 * K1 * dHdydy * cosarg * J1 - K1 * dHdy * cosarg * dJ1 * dHdy - K1 * dHdydy * sinarg * Y1
4382 - K1 * dHdy * sinarg * dY1 * dHdy + K2 * dHdxdydy * sinarg * J1 + K2 * dHdxdy * sinarg * dJ1 * dHdy
4383 - K2 * dHdxdydy * cosarg * Y1 - K2 * dHdxdy * cosarg * dY1 * dHdy + K2 * dHdxdy * dHdy * sinarg * J0
4384 + K2 * dHdx * dHdydy * sinarg * J0 - K2 * dHdx * dHdy * sinarg * J1 * dHdy - K2 * dHdxfHdy * dHdy * sinarg * J1
4385 - K2 * dHdxfH * dHdydy * sinarg * J1 - K2 * dHdxfH * dHdy * sinarg * dJ1 * dHdy
4386 - K2 * dHdxdy * dHdy * cosarg * Y0 - K2 * dHdx * dHdydy * cosarg * Y0 + K2 * dHdx * dHdy * cosarg * Y1 * dHdy
4387 + K2 * dHdxfHdy * dHdy * cosarg * Y1 + K2 * dHdxfH * dHdydy * cosarg * Y1
4388 + K2 * dHdxfH * dHdy * cosarg * dY1 * dHdy;
4389
4390 const MFloat pp = -1.0 * rho0 * (dPhidt + u0 * dPhidx);
4391
4392 if(param.perturbed) {
4393 vars.u[sample] = dPhidx;
4394 vars.v[sample] = dPhidy;
4395 vars.p[sample] = pp;
4396 } else {
4397 vars.u[sample] = dPhidx + u0;
4398 vars.v[sample] = dPhidy;
4399 vars.p[sample] = pp + p0;
4400 vars.rho[sample] = rho0 + pp / (c0 * c0);
4401 }
4402 transformBackToOriginalCoordinate2D(&vars.u[sample], &vars.v[sample]);
4403 }
4404}
4405
4406
4408template <MInt nDim>
4410 MFloat x = coord[0];
4411 MFloat y = coord[1];
4412 MFloat z = coord[2];
4413 transformCoordinatesToFlowDirection3D(&x, &y, &z);
4414
4415 const MFloat omega = param.omega;
4416 const MFloat c0 = 1.0; // by definition
4417 const MFloat rho0 = param.rho0;
4418 const MFloat p0 = rho0 * c0 * c0 / m_gamma;
4419 const MFloat beta = param.beta;
4420 const MFloat amplitude = param.amplitude;
4421 const MFloat u0 = param.Ma * c0;
4422 const MFloat mach = param.Ma;
4423
4424 MFloat k = omega / c0;
4425
4426 const MFloat beta2 = beta * beta;
4427 const MFloat fbeta2 = 1 / beta2;
4428 const MFloat d = sqrt(x * x + beta * beta * (y * y + z * z));
4429 const MFloat d2 = d * d;
4430 const MFloat d3 = d2 * d;
4431 const MFloat fd2 = 1 / d2;
4432 const MFloat fd3 = 1 / d3;
4433 const MFloat C1 = amplitude / (4 * PI);
4434 const MFloat C2 = k * fbeta2;
4435
4436 const MFloat dargdx = -1.0 * C2 * (x / d - mach);
4437 const MFloat dargdy = -1.0 * k * y / d;
4438 const MFloat dargdz = -1.0 * k * z / d;
4439
4440 for(MInt sample = 0; sample < noSamples(); sample++) {
4441 // Note: adapted from acoupot3d/acou_pot.cpp::calc_p/ux/uy/uz_analytic()
4442 const MFloat time = sample * m_dt;
4443 const MFloat arg = omega * time - k * (d - mach * x) * fbeta2;
4444
4445 const MFloat cosarg = cos(arg);
4446 const MFloat sinarg = sin(arg);
4447
4448 const MFloat dPhidt = -1.0 * C1 * 3 * x * y * beta2 * fd2 * fd3 * sinarg * omega
4449 - C1 * 3 * k * x * y * fd2 * fd2 * cosarg * omega + C1 * k * C2 * x * y * fd3 * sinarg * omega
4450 + C1 * mach * k * y * fd3 * cosarg * omega - C1 * mach * k * C2 * y * fd2 * sinarg * omega;
4451
4452 const MFloat dPhidx =
4453 C1 * 3 * y * beta2 * (fd2 * fd3 - 5 * x * x * fd2 * fd2 * fd3) * cosarg
4454 - C1 * 3 * x * y * beta2 * fd2 * fd3 * sinarg * dargdx
4455 - C1 * 3 * k * y * (fd2 * fd2 - 4 * x * x * fd3 * fd3) * sinarg
4456 - C1 * 3 * k * x * y * fd2 * fd2 * cosarg * dargdx - C1 * k * C2 * y * (fd3 - 3 * x * x * fd3 * fd2) * cosarg
4457 + C1 * k * C2 * x * y * fd3 * sinarg * dargdx - C1 * mach * k * y * 3 * x * fd3 * fd2 * sinarg
4458 + C1 * mach * k * y * fd3 * cosarg * dargdx - C1 * mach * k * C2 * y * 2 * x * fd2 * fd2 * cosarg
4459 - C1 * mach * k * C2 * y * fd2 * sinarg * dargdx;
4460
4461 const MFloat dPhidy =
4462 C1 * 3 * x * beta2 * (fd2 * fd3 - 5 * beta2 * y * y * fd2 * fd2 * fd3) * cosarg
4463 - C1 * 3 * x * y * beta * beta * fd3 * fd2 * sinarg * dargdy
4464 - C1 * 3 * k * x * (fd2 * fd2 - 4 * beta2 * y * y * fd3 * fd3) * sinarg
4465 - C1 * 3 * k * x * y * fd2 * fd2 * cosarg * dargdy
4466 - C1 * k * C2 * x * (fd3 - 3 * beta2 * y * y * fd2 * fd3) * cosarg + C1 * k * C2 * x * y * fd3 * sinarg * dargdy
4467 + C1 * mach * k * (fd3 - 3 * beta2 * y * y * fd2 * fd3) * sinarg + C1 * mach * k * y * fd3 * cosarg * dargdy
4468 + C1 * mach * k * C2 * (fd2 - 2 * beta2 * y * y * fd2 * fd2) * cosarg
4469 - C1 * mach * k * C2 * y * fd2 * sinarg * dargdy;
4470
4471 const MFloat dPhidz =
4472 -1.0 * C1 * 15 * x * y * z * beta2 * beta2 * fd2 * fd2 * fd3 * cosarg
4473 - C1 * 3 * x * y * beta2 * fd2 * fd3 * sinarg * dargdz + C1 * 12 * k * x * y * z * beta2 * fd3 * fd3 * sinarg
4474 - C1 * 3 * k * x * y * fd2 * fd2 * cosarg * dargdz + C1 * 3 * k * k * x * y * z * fd2 * fd3 * cosarg
4475 + C1 * k * C2 * x * y * fd3 * sinarg * dargdz - C1 * 3 * mach * k * y * z * beta2 * fd2 * fd3 * sinarg
4476 + C1 * mach * k * y * fd3 * cosarg * dargdz - C1 * 2 * mach * k * k * y * z * fd2 * fd2 * cosarg
4477 - C1 * mach * k * C2 * y * fd2 * sinarg * dargdz;
4478
4479 const MFloat pp = -1.0 * rho0 * (dPhidt + u0 * dPhidx);
4480
4481 if(param.perturbed) {
4482 vars.u[sample] = dPhidx;
4483 vars.v[sample] = dPhidy;
4484 vars.w[sample] = dPhidz;
4485 vars.p[sample] = pp;
4486 } else {
4487 vars.u[sample] = dPhidx + u0;
4488 vars.v[sample] = dPhidy;
4489 vars.w[sample] = dPhidz;
4490 vars.p[sample] = pp + p0;
4491 vars.rho[sample] = rho0 + pp / (c0 * c0);
4492 }
4493 transformBackToOriginalCoordinate3D(&vars.u[sample], &vars.v[sample], &vars.w[sample]);
4494 }
4495}
4496
4505template <MInt nDim>
4507 SourceVars& vars) {
4508 const MFloat x = coord[0];
4509 const MFloat y = coord[1];
4510 constexpr MFloat cs = 1.0; // by definition
4511 const MFloat rho0 = param.rho0;
4512
4513 const MFloat fac = cs / (2.0 * PI);
4514 for(MInt sample = 0; sample < noSamples(); sample++) {
4515 const MFloat t = sample * m_dt;
4516 const MFloat xRel = x - param.Ma * cs * t;
4517 const MFloat yRel = y;
4518 const MFloat r = sqrt(POW2(xRel) + POW2(yRel));
4519 const MFloat theta = atan2(yRel, xRel);
4520 const MFloat utheta = fac * r * exp(0.5 * (1 - r * r));
4521 const MFloat up = utheta * sin(theta);
4522 const MFloat vp = -utheta * cos(theta);
4523 const MFloat pp = pow(1 + (m_gamma - 1) * exp(1 - r * r) / (8.0 * PI * PI), m_gamma / (m_gamma - 1.0)) - 1.0;
4524 if(param.perturbed) {
4525 vars.u[sample] = up;
4526 vars.v[sample] = vp;
4527 vars.p[sample] = pp;
4528 } else {
4529 vars.u[sample] = param.Ma * cs + up;
4530 vars.v[sample] = vp;
4531 vars.p[sample] = pp;
4532 if(vars.rho != nullptr) vars.rho[sample] = rho0 * pow((1.0 + pp), 1.0 / m_gamma);
4533 }
4534 }
4535}
4536
4546template <MInt nDim>
4547void AcaSolver<nDim>::applySymmetryBc(const std::array<MFloat, nDim> coord, MFloat* const p_complexVariables) {
4548 TRACE();
4549 const MInt noSym = m_symBc.size();
4550 if(noSym == 0) return;
4551 TERMM_IF_COND(noSym > 1, "WARNING: only implemented for one symmetry BC so far");
4552 auto transform = [](std::array<MFloat, nDim>& coord_, const AcaSymBc& bc) {
4553 const MFloat coordTimesNormal = std::inner_product(&coord_[0], &coord_[nDim], &bc.normal[0], .0);
4554 for(MInt d = 0; d < nDim; d++) {
4555 coord_[d] += -2 * coordTimesNormal * bc.normal[d] + 2 * bc.origin[d];
4556 }
4557 };
4558 MFloatScratchSpace complexVariables_(2 * noSamples(), AT_, "complexVariables_");
4559 // transform observer position
4560 std::array<MFloat, nDim> symCoord = coord;
4561 transform(symCoord, m_symBc[0]);
4562 // Calculate pressure for transformed point before adding to its 'image' observer
4563 calcSurfaceIntegralsForObserver(symCoord, complexVariables_.data());
4564 for(MInt i = 0; i < noSamples(); i++) {
4565 p_complexVariables[2 * i + 0] += complexVariables_[2 * i + 0];
4566 p_complexVariables[2 * i + 1] += complexVariables_[2 * i + 1];
4567 }
4568}
4569
4571template <MInt nDim>
4573 TRACE();
4574 RECORD_TIMER_START(m_timers[Timers::InitObservers]);
4575
4576 MInt noGlobalObservers = 0;
4577 printMessage("- Init observer points");
4578 std::stringstream ss;
4579
4580 if(m_generateObservers) { // TODO labels:ACA,toenhance make more general/flexible
4581 MString observerGenerationType = "CIRCLE";
4582 observerGenerationType =
4583 Context::getSolverProperty<MString>("observerGenerationType", m_solverId, AT_, &observerGenerationType);
4593 noGlobalObservers = 100;
4594 noGlobalObservers = Context::getSolverProperty<MInt>("noObservers", m_solverId, AT_, &noGlobalObservers);
4595 if(noGlobalObservers < 1) {
4596 TERMM(1, "The number of Observers cannot be lower than 1!");
4597 }
4598 m_globalObserverCoordinates.resize(noGlobalObservers * nDim);
4599
4600 ss << " "
4601 << "Generate " << noGlobalObservers << " observer with mode " << observerGenerationType;
4602
4603 if(observerGenerationType == MString("CIRCLE")) {
4604 // Default: 100 points on a circle with R=50
4605
4606 const MFloat step_rad = 2 * PI / static_cast<MFloat>(noGlobalObservers);
4615 MFloat radius = 50.0;
4616 radius = Context::getSolverProperty<MFloat>("observerRadius", m_solverId, AT_, &radius);
4617 if(radius <= 0.0) {
4618 TERMM(1, "The radius cannot be equal or lower than 0!");
4619 }
4620
4621 for(MInt i = 0; i < noGlobalObservers; i++) {
4622 // TODO labels:ACA properties for center
4623 const MFloat x = radius * cos(i * step_rad);
4624 const MFloat y = radius * sin(i * step_rad);
4625
4626 m_globalObserverCoordinates[i * nDim] = x;
4627 m_globalObserverCoordinates[i * nDim + 1] = y;
4628 if constexpr(nDim == 3) {
4629 m_globalObserverCoordinates[i * nDim + 2] = 0.0; // TODO labels:ACA make more flexible for 3D
4630 }
4631 }
4632
4633 } else if(observerGenerationType == MString("PLANE")) {
4634 // input
4635 MInt normalDir = 2;
4636 if constexpr(nDim > 2) {
4637 normalDir = Context::getSolverProperty<MInt>("observerGenerationPlaneNormal", m_solverId, AT_, &normalDir);
4638 }
4639 MFloat dim[4];
4640 for(MInt i = 0; i < 4; i++) {
4641 dim[i] = Context::getSolverProperty<MFloat>("observerGenerationPlaneDimension", m_solverId, AT_, i);
4642 }
4643 // Specify spacing dx[] in the plane after determining number of points
4644 // per direction nx[]
4645 const MFloat l[2] = {dim[2] - dim[0], dim[3] - dim[1]};
4646 MInt nx[2];
4647 nx[0] = sqrt(noGlobalObservers * l[0] / l[1]);
4648 nx[1] = noGlobalObservers / nx[0];
4649 const MFloat dx[2] = {l[0] / nx[0], l[1] / nx[1]};
4650 // generate points
4651 for(MInt i = 0; i < noGlobalObservers; i++) {
4652 const MInt j = i % nx[0];
4653 const MInt k = i / nx[0];
4654 m_globalObserverCoordinates[i * nDim + 0] = dim[0] + dx[0] * j;
4655 m_globalObserverCoordinates[i * nDim + 1] = dim[1] + dx[1] * k;
4656 }
4657 } else {
4658 ss << " is not possible.";
4659 }
4660 } else if(m_observerFileName != "") {
4661 // Read number of observers and m_globalObserverCoordinates from given file on rank 0
4662 if(domainId() == 0) {
4663 noGlobalObservers = loadPointCoordinatesFromFile(m_observerFileName, nDim, m_globalObserverCoordinates);
4664
4665 if(noGlobalObservers < 1) {
4666 TERMM(1, "Error: no observer points found in file " + m_observerFileName);
4667 }
4668 ss << " "
4669 << "Read " << noGlobalObservers << " observer points from file " << m_observerFileName;
4670 }
4671
4672 // Broadcast number of observers to all ranks
4673 MPI_Bcast(&noGlobalObservers, 1, type_traits<MInt>::mpiType(), 0, mpiComm(), AT_, "noObservers");
4674
4675 // Resize storage for coordinates on other ranks
4676 if(domainId() != 0) {
4677 m_globalObserverCoordinates.resize(noGlobalObservers * nDim);
4678 }
4679
4680 // Broadcast observer coordinates from rank 0
4681 MPI_Bcast(m_globalObserverCoordinates.data(), noGlobalObservers * nDim, type_traits<MFloat>::mpiType(), 0,
4682 mpiComm(), AT_, "coordinates");
4683 } else {
4684 TERMM(1, "Error: no observer file name specified and generation of observers not enabled.");
4685 }
4686
4687 m_noGlobalObservers = noGlobalObservers;
4688
4689 // number of observer (complex) variables, for now just the acoustic pressure
4690 const MInt noObsVars = 1;
4691 const MInt noObsComplexVars = 1;
4692
4693 // Set data sizes in observer collector
4694 m_observerData.setNoVars(noObsVars);
4695 m_observerData.setNoComplexVars(noObsComplexVars);
4696 m_observerData.setNoSamples(m_noSamples);
4697
4698 // Calculate offsets and number of local observers
4699 distributeObservers();
4700
4701 // Resize observer collector only for local observer data !
4702 m_observerData.reset(noObservers());
4703
4704 // Add (local) observers to collector and set data
4705 for(MInt localObserverId = 0; localObserverId < noObservers(); localObserverId++) {
4706 m_observerData.append();
4707 const MInt globalObserverId = a_globalObserverId(localObserverId);
4708 MFloat* obsCoord = &m_observerData.observerCoordinates(localObserverId);
4709 for(MInt d = 0; d < nDim; d++) {
4710 obsCoord[d] = m_globalObserverCoordinates[globalObserverId * nDim + d];
4711 }
4712 }
4713
4714 printMessage(ss.str());
4715 RECORD_TIMER_STOP(m_timers[Timers::InitObservers]);
4716}
4717
4719// in-/output dimension and the dimensions used in ACA
4720template <MInt nDim>
4722 // TODO labels:ACA IMHO it does not make sense to have a different dimensions
4723 // or even quantities (wave number or frequency) in the output. The output
4724 // should have the same dimensions for a certain solver to avoid confusion
4725 // when switching between different input solver.
4726 std::stringstream ss;
4727 printMessage("- Init conversion factors");
4728 switch(m_acaResultsNondimMode) {
4729 case NONDIMACA:
4730 case NONDIMSTAGNATION: {
4731 const MFloat constant = 1.0 + (m_gamma - 1.0) * 0.5 * m_MaDim * m_MaDim;
4732 m_input2Aca.length = 1.0;
4733 m_input2Aca.density = pow(constant, (1.0 / (m_gamma - 1.0)));
4734 m_input2Aca.velocity = sqrt(constant);
4735 m_input2Aca.time = m_input2Aca.length / m_input2Aca.velocity;
4736 m_input2Aca.pressure = pow(constant, (m_gamma / (m_gamma - 1.0)));
4737 constexpr MFloat HzToRad = 2.0 * PI;
4738 m_input2Aca.frequency = HzToRad / m_input2Aca.time;
4739 // .. and corresponding inverse factors
4740 if(m_acaResultsNondimMode == NONDIMACA) {
4741 // Default definition is correct -> dump out in ACA dimension
4742 } else if(m_acaResultsNondimMode == NONDIMSTAGNATION) {
4743 m_aca2Output.length = 1.0 / m_input2Aca.length;
4744 m_aca2Output.density = 1.0 / m_input2Aca.density;
4745 m_aca2Output.velocity = 1.0 / m_input2Aca.velocity;
4746 m_aca2Output.time = 1.0 / m_input2Aca.time;
4747 m_aca2Output.pressure = 1.0 / m_input2Aca.pressure;
4748 m_aca2Output.frequency = 1.0 / m_input2Aca.frequency;
4749 }
4750 break;
4751 }
4752 case NONDIMLB: {
4753 // To reduce some confusion of the LB user. In LB all quantities are made
4754 // non-dimensional with dxLb (cell length at max level), density at
4755 // infinity, and xi_0=dxLb/dtLb. But, the time in the surface sampling
4756 // file is given by t_infty = t * u_infty / L_FV , L_FV=1.
4757 m_input2Aca.density = 1.0;
4758 m_input2Aca.time = 1.0 / m_MaDim; // not LB to ACA, as in sampling file t_infty is dumped
4759 // TODO labels:ACA How to choose dxLb (question of input only)
4760 // const MFloat dxLb = Context::getSolverProperty<MFloat>("dxLb", m_solverId, AT_, 0);
4761 // m_input2Aca.length = dxLb;
4762 m_input2Aca.length = std::numeric_limits<double>::quiet_NaN();
4763 m_input2Aca.velocity = F1BCS;
4764 m_input2Aca.pressure = m_input2Aca.density * POW2(m_input2Aca.velocity);
4765 break;
4766 }
4767 default: {
4768 TERMM(1, "Invalid acaResultsNondimMode: '" + std::to_string(m_acaResultsNondimMode) + "'");
4769 }
4770 }
4771 ss << " MaDim = " << m_MaDim << std::endl;
4772 ss << " Conversion factors input2Aca | aca2Output:" << std::endl;
4773 ss << " velocity = " << m_input2Aca.velocity << " | " << m_aca2Output.velocity << std::endl;
4774 ss << " pressure = " << m_input2Aca.pressure << " | " << m_aca2Output.pressure << std::endl;
4775 ss << " density = " << m_input2Aca.density << " | " << m_aca2Output.density << std::endl;
4776 ss << " time = " << m_input2Aca.time << " | " << m_aca2Output.time << std::endl;
4777 ss << " frequency= " << m_input2Aca.frequency << " | " << m_aca2Output.frequency;
4778 printMessage(ss.str());
4779}
4780
4783template <MInt nDim>
4785 // All variables which are read in need to be non-dimensionalized with the freestream c and rho.
4786 // Index 0 in this (and only in this) function descripes the stagnation state.
4787 m_log << "Change dimensions from stagnation to freestream state" << std::endl;
4788
4789 // APE or not APE? That is the question
4790 const MInt dummy_u = (m_acousticMethod == FWH_METHOD) ? FWH::U : FWH_APE::UP;
4791 const MInt dummy_v = (m_acousticMethod == FWH_METHOD) ? FWH::V : FWH_APE::VP;
4792 const MInt dummy_w = (m_acousticMethod == FWH_METHOD) ? FWH::W : FWH_APE::WP;
4793 const MInt dummy_p = (m_acousticMethod == FWH_METHOD) ? FWH::P : FWH_APE::PP;
4794
4795 // Apply norm factor to the variables
4796 for(MInt seg = 0; seg < totalNoSurfaceElements(); seg++) {
4797 for(MInt t = 0; t < noSamples(); t++) {
4798 m_surfaceData.variables(seg, dummy_u, t) *= m_input2Aca.velocity;
4799 m_surfaceData.variables(seg, dummy_v, t) *= m_input2Aca.velocity;
4800 if constexpr(nDim == 3) m_surfaceData.variables(seg, dummy_w, t) *= m_input2Aca.velocity;
4801 m_surfaceData.variables(seg, dummy_p, t) *= m_input2Aca.pressure;
4802 if(m_acousticMethod == FWH_METHOD) {
4803 m_surfaceData.variables(seg, FWH::RHO, t) *= m_input2Aca.density;
4804 }
4805 } // end of sample
4806 } // end of seg
4807
4808 // Norm factor for time:
4809 for(MInt i = 0; i < noSamples(); i++) {
4810 m_times[i] *= m_input2Aca.time;
4811 }
4812}
4813
4814
4815template <MInt nDim>
4817 printMessage("- Change dimension of observer data");
4818 if(approx(m_aca2Output.frequency, 1.0, MFloatEps)) {
4819 for(MInt i = 0; i < noSamples(); i++) {
4820 m_frequencies[i] *= m_aca2Output.frequency;
4821 }
4822 }
4823 if(approx(m_aca2Output.pressure, 1.0, MFloatEps)) {
4824 for(MInt obs = 0; obs < noObservers(); obs++) {
4825 for(MInt t = 0; t < noSamples(); t++) {
4826 m_observerData.variables(obs, 0, t) *= m_aca2Output.pressure;
4827 }
4828 }
4829 for(MInt obs = 0; obs < noObservers(); obs++) {
4830 for(MInt f = 0; f < noSamples(); f++) {
4831 m_observerData.complexVariables(obs, 0, f, 0) *= m_aca2Output.pressure;
4832 m_observerData.complexVariables(obs, 0, f, 1) *= m_aca2Output.pressure;
4833 }
4834 }
4835 }
4836 if(approx(m_aca2Output.time, 1.0, MFloatEps)) {
4837 m_dt *= m_aca2Output.time;
4838 for(MInt i = 0; i < noSamples(); i++) {
4839 m_times[i] *= m_aca2Output.time;
4840 }
4841 }
4842}
4843
4844template <MInt nDim>
4846 // Compute time step size
4847 m_dt = (m_times[noSamples() - 1] - m_times[0]) / (static_cast<MFloat>(noSamples() - 1));
4848
4849 // Check for equidistant timesteps - necessary for the computation!
4850 for(MInt i = 1; i < noSamples(); i++) {
4851 const MFloat delta = m_times[i] - m_times[i - 1];
4852 const MFloat relErr = std::fabs(m_dt - delta) / m_dt;
4853 if(relErr > 1e-10) {
4854 std::ostringstream err;
4855 err << std::setprecision(12) << std::scientific << "Error: time step size mismatch; m_dt = " << m_dt
4856 << "; delta = " << delta << "; relErr = " << relErr << "; i = " << i << "; times[i] = " << m_times[i]
4857 << "; times[i-1] = " << m_times[i - 1];
4858 TERMM(1, err.str());
4859 }
4860 }
4861}
4862
4863template <MInt nDim>
4865 // Check if noSamples is a potency of 2 to see if FFT can be used
4866 MInt numSampl = noSamples();
4867 MInt prove = 1;
4868 m_FastFourier = false;
4869
4870 // if potency of 2: numSampl == 0; if not: numSampl == 1
4871 while(numSampl > 1) {
4872 numSampl /= 2;
4873 prove *= 2;
4874 }
4875 if(prove == noSamples()) {
4876 m_FastFourier = true;
4877 m_log << " >> NOTE: Number of samples is a potency of 2. Thus Fast Fourier Transform can "
4878 "be used, if not defined otherwise (transformationType = 0 for DFT)."
4879 << std::endl;
4880 } else { // prove != noSamples()
4881 m_FastFourier = false;
4882 m_transformationType = 0;
4883 m_log << " >> NOTE: Number of samples is not a potency of 2. Thus Fast Fourier Transform "
4884 "cannot be used, no matter what."
4885 << std::endl;
4886 }
4887}
4888
4889// Only works with analytical test cases
4890// TODO labels:ACA,cleanup check/cleanup
4891template <MInt nDim>
4893 TERMM(1, "TODO check this function");
4894 if(domainId() == 0) {
4895 std::cout << "8. Starting accuarcy calculation." << std::endl;
4896
4897 // These parameters have to be set
4898 // -------------------
4899 const MInt observer = globalNoObservers();
4900 MInt noSegs = 100;
4901 noSegs = Context::getSolverProperty<MFloat>("noSegments_" + std::to_string(0), m_solverId, AT_, 0);
4902 if(noSegs == 0) {
4903 noSegs = Context::getSolverProperty<MFloat>("noSegments_" + std::to_string(0), m_solverId, AT_, 1);
4904 }
4905
4906 MFloat box_size = 2.0 * 2;
4907 box_size = Context::getSolverProperty<MFloat>("surfaceCoordinates_" + std::to_string(0), m_solverId, AT_, 0);
4908 box_size = 2.0 * std::fabs(box_size);
4909 // --------------------
4910
4911 // Alternative expression: SamplePoints per Period even with flow
4912 MFloat noPeriods = 1.0;
4913 noPeriods = Context::getSolverProperty<MFloat>("noPeriods", m_solverId, AT_, &noPeriods);
4914
4915 MFloat omega_factor = 4.0;
4916 omega_factor = Context::getSolverProperty<MFloat>("omega_factor", m_solverId, AT_, &omega_factor);
4917
4918 MFloat Delta_Seg = box_size / noSegs;
4919 if constexpr(nDim == 3) {
4920 Delta_Seg *= Delta_Seg;
4921 }
4922
4923 // Wave length lambda
4924 m_lambdaZero = 2.0 / m_omega_factor;
4925 m_lambdaMach = 2.0 * (1.0 - m_Ma) / m_omega_factor;
4926
4927 // minimal resolution of one period according to the thesis.
4928 const MFloat R_min = noSamples() * m_lambdaMach / (noPeriods * m_lambdaZero);
4929 const MFloat Z = Delta_Seg / R_min;
4930 const MFloat Y = Delta_Seg / m_lambdaMach;
4931
4932 // Read in the analytic solution for even periods to be able to compare with uneven periods
4933 std::vector<MFloat> Analytic_EvenPeriods(globalNoObservers() * noSamples() / 2 * 5, 0.0);
4934 std::vector<MFloat> Analytic_EvenPeriods_Amplitude(globalNoObservers() * noSamples() / 2, 0.0);
4935 std::vector<MFloat> Analytic_EvenPeriods_Frequencies(globalNoObservers() * noSamples() / 2, 0.0);
4936 std::vector<MFloat> RMS_EvenPeriods(globalNoObservers(), 0.0);
4937
4938 const MInt EP_Samples = 64;
4939 if(noSamples() == EP_Samples) {
4940 std::ifstream file;
4941 MString line;
4942 MString Name = "./test/AnalyticSolution_ForEvenPeriods.dat";
4943 file.open(Name, std::ios::in);
4944 MInt count = 0;
4945 MFloat i1, i2, i3, i4, i5;
4946 if(file.is_open()) {
4947 while(getline(file, line)) {
4948 std::stringstream linebuffer(line);
4949 linebuffer >> i1 >> i2 >> i3 >> i4 >> i5;
4950 Analytic_EvenPeriods[5 * count] = i1;
4951 Analytic_EvenPeriods[5 * count + 1] = i2;
4952 Analytic_EvenPeriods[5 * count + 2] = i3;
4953 Analytic_EvenPeriods[5 * count + 3] = i4;
4954 Analytic_EvenPeriods[5 * count + 4] = i5;
4955
4956 Analytic_EvenPeriods_Amplitude[count] = i4;
4957 Analytic_EvenPeriods_Frequencies[count] = i1;
4958 count += 1;
4959 }
4960 } else {
4961 std::cerr << "Fehler beim Öffnen der Datei: " << Name << "." << std::endl;
4962 }
4963
4964 std::ifstream file1;
4965 MString line1;
4966 MString Name1 = "./test/RMSEvenPeriods.dat";
4967 file1.open(Name1, std::ios::in);
4968 MInt count1 = 0;
4969 MFloat i_1, i_2;
4970 if(file1.is_open()) {
4971 while(getline(file1, line1)) {
4972 std::stringstream linebuffer(line1);
4973 linebuffer >> i_1 >> i_2;
4974 RMS_EvenPeriods[count1] = i_2;
4975 count1 += 1;
4976 }
4977 } else {
4978 std::cerr << "Fehler beim Öffnen der Datei: " << Name1 << "." << std::endl;
4979 }
4980 }
4981 // RMS PRESSURE
4982 std::vector<MFloat> p_rms(globalNoObservers(), 0.0);
4983
4984 for(MInt obs = 0; obs < globalNoObservers(); obs++) {
4985 for(MInt t = 0; t < noSamples(); t++) {
4986 p_rms[obs] += m_observerData.variables(obs, 0, t) * m_observerData.variables(obs, 0, t);
4987 }
4988 p_rms[obs] = sqrt(p_rms[obs] / noSamples());
4989 }
4990
4991 // Quality (Variance) check
4992 MFloat max_diff = 0.0;
4993 MFloat max_diff_2 = 0.0;
4994 MFloat diff_normalized = 0.0;
4995 MFloat diff_2_normalized = 0.0;
4996 MFloat sum_diff_sq = 0.0;
4997 MFloat sum_diff_2_sq = 0.0;
4998 MFloat sum_analytic_sq = 0.0;
4999 MFloat sum_a_ep_sq = 0.0;
5000 std::vector<MFloat> diff(observer, 0.0);
5001 std::vector<MFloat> diff_2(observer, 0.0);
5002 for(MInt i = 0; i < observer; i++) {
5003 diff[i] = std::fabs(m_rmsP_Analytic[i] - p_rms[i]);
5004 sum_diff_sq += diff[i] * diff[i];
5005 sum_analytic_sq += m_rmsP_Analytic[i];
5006 diff_normalized = diff[i] / m_rmsP_Analytic[i] * 100;
5007
5008 // In case of uneven periods
5009 if(noSamples() == EP_Samples) {
5010 diff_2[i] = std::fabs(RMS_EvenPeriods[i] - p_rms[i]);
5011 sum_diff_2_sq += diff_2[i] * diff_2[i];
5012 sum_a_ep_sq += RMS_EvenPeriods[i];
5013 diff_2_normalized = diff_2[i] / RMS_EvenPeriods[i] * 100;
5014 }
5015 if(diff_normalized >= max_diff) {
5016 if(m_rmsP_Analytic[i] > 10e-13) {
5017 max_diff = diff_normalized; // [%]
5018 }
5019 if(EP_Samples == 64) {
5020 max_diff_2 = diff_2_normalized; //[%]
5021 }
5022 }
5023 }
5024 const MFloat value_top = sqrt(sum_diff_sq / globalNoObservers());
5025 const MFloat value_bot = sum_analytic_sq / globalNoObservers();
5026 const MFloat Q_C = 100 * value_top / value_bot; // [%]
5027
5028 // In case of uneven periods
5029 MFloat Q_C_UnevenPeriods = 0.0;
5030 if(noSamples() == EP_Samples) {
5031 const MFloat value_top_UP = sqrt(sum_diff_2_sq / globalNoObservers());
5032 const MFloat value_bot_UP = sum_a_ep_sq / globalNoObservers();
5033 Q_C_UnevenPeriods = 100 * value_top_UP / value_bot_UP;
5034 }
5035
5036 // Frequency, Amplitude comparison for all observers
5037 std::vector<MFloat> diff_Ampl(globalNoObservers() * noSamples(), 0.0);
5038 std::vector<MFloat> max_Ampl(globalNoObservers(), 0.0);
5039 std::vector<MFloat> max_Ampl_2(globalNoObservers(), 0.0);
5040 std::vector<MFloat> max_Ampl_a(globalNoObservers(), 0.0);
5041 std::vector<MFloat> max_Ampl_A_EvenPeriods(globalNoObservers(), 0.0);
5042 std::vector<MFloat> max_Ampl_Diff(globalNoObservers(), 0.0);
5043 std::vector<MFloat> max_Ampl_Diff_2(globalNoObservers(), 0.0);
5044 std::vector<MFloat> max_Ampl_Diff_normalized(globalNoObservers(), 0.0);
5045 std::vector<MFloat> max_Ampl_Diff_normalized_2(globalNoObservers(), 0.0);
5046 std::vector<MFloat> freq_At_Max_Ampl(globalNoObservers(), 0.0);
5047 std::vector<MFloat> freq_At_Max_Ampl_2(globalNoObservers(), 0.0);
5048 std::vector<MFloat> freq_At_Max_Ampl_a(globalNoObservers(), 0.0);
5049 std::vector<MFloat> freq_At_Max_Ampl_A_EvenPeriods(globalNoObservers(), 0.0);
5050 std::vector<MFloat> freq_Diff(globalNoObservers(), 0.0);
5051 std::vector<MFloat> freq_Diff_2(globalNoObservers(), 0.0);
5052 std::vector<MFloat> freq_Diff_normalized(globalNoObservers(), 0.0);
5053 std::vector<MFloat> freq_Diff_normalized_2(globalNoObservers(), 0.0);
5054 std::vector<MFloat> mean_Ampl_analytic(globalNoObservers(), 0.0);
5055 std::vector<MFloat> sum_Diff_Amplitudes_UnevenPeriods(globalNoObservers(), 0.0);
5056 std::vector<MFloat> SideLobes(globalNoObservers(), 0.0);
5057 MFloat sum_max_Ampl_Diff_normalized = 0.0;
5058 MFloat sum_freq_Diff_normalized = 0.0;
5059 MFloat sum_max_Ampl_Diff_normalized_2 = 0.0;
5060 MFloat sum_freq_Diff_normalized_2 = 0.0;
5061 MFloat sum_SideLobes = 0.0;
5062 MFloat Amplitude_EvenPeriods = 0.0;
5063 for(MInt obs = 0; obs < globalNoObservers(); obs++) {
5064 for(MInt i = 1; i < noSamples() / 2 + 1; i++) {
5065 const MFloat real_c = m_observerData.complexVariables(obs, 0, i, 0);
5066 const MFloat imag_c = m_observerData.complexVariables(obs, 0, i, 1);
5067 const MFloat real_a = m_Analyticfreq[obs * noSamples() * 2 + 2 * i];
5068 const MFloat imag_a = m_Analyticfreq[obs * noSamples() * 2 + 2 * i + 1];
5069 const MFloat Amplitude_c = 2 * sqrt(real_c * real_c + imag_c * imag_c);
5070 const MFloat Amplitude_a = 2 * sqrt(real_a * real_a + imag_a * imag_a);
5071 // Comparison with even periods in case uneven periods are used
5072 if(noSamples() == EP_Samples) {
5073 Amplitude_EvenPeriods = Analytic_EvenPeriods_Amplitude[obs * noSamples() / 2 + (i - 1)];
5074
5075 // Get Frequency at which the analytic source for even periods oscillates
5076 if(Amplitude_EvenPeriods > max_Ampl_A_EvenPeriods[obs]) {
5077 freq_At_Max_Ampl_A_EvenPeriods[obs] = Analytic_EvenPeriods_Frequencies[obs * noSamples() / 2 + (i - 1)];
5078 max_Ampl_A_EvenPeriods[obs] = Amplitude_EvenPeriods;
5079 }
5080 }
5081
5082 // Get Frequency at which analytic source oscillates
5083 if(Amplitude_a > max_Ampl_a[obs]) {
5084 freq_At_Max_Ampl_a[obs] = m_frequencies[i];
5085 max_Ampl_a[obs] = Amplitude_a;
5086 }
5087
5088 // Get max Amplitude computed and its frequency
5089 if(Amplitude_c > max_Ampl[obs]) {
5090 freq_At_Max_Ampl[obs] = m_frequencies[i];
5091 max_Ampl[obs] = Amplitude_c;
5092 }
5093
5094 // Side lobes
5095 SideLobes[obs] += std::fabs(Amplitude_EvenPeriods - Amplitude_c);
5096 }
5097 // Normalize the maximum difference in amplitude and frequency with the maximum analytical
5098 // ampl. and freq. Index 2 is for uneven Periods comparison
5099 max_Ampl_Diff[obs] = std::fabs(max_Ampl_a[obs] - max_Ampl[obs]);
5100 freq_Diff[obs] = std::fabs(freq_At_Max_Ampl[obs] - freq_At_Max_Ampl_a[obs]);
5101
5102 max_Ampl_Diff_normalized[obs] = max_Ampl_Diff[obs] / max_Ampl_a[obs];
5103 freq_Diff_normalized[obs] = freq_Diff[obs] / freq_At_Max_Ampl_a[obs];
5104
5105 sum_max_Ampl_Diff_normalized += max_Ampl_Diff_normalized[obs];
5106 sum_freq_Diff_normalized += freq_Diff_normalized[obs];
5107
5108 if(noSamples() == EP_Samples) {
5109 max_Ampl_Diff_2[obs] = std::fabs(max_Ampl_A_EvenPeriods[obs] - max_Ampl[obs]);
5110 freq_Diff_2[obs] = std::fabs(freq_At_Max_Ampl[obs] - freq_At_Max_Ampl_A_EvenPeriods[obs]);
5111 sum_SideLobes += (SideLobes[obs] / (noSamples() / 2));
5112
5113 max_Ampl_Diff_normalized_2[obs] = max_Ampl_Diff_2[obs] / max_Ampl_A_EvenPeriods[obs];
5114 freq_Diff_normalized_2[obs] = freq_Diff_2[obs] / freq_At_Max_Ampl_A_EvenPeriods[obs];
5115
5116 // In case of uneven and even periods comparison
5117 sum_max_Ampl_Diff_normalized_2 += max_Ampl_Diff_normalized_2[obs];
5118 sum_freq_Diff_normalized_2 += freq_Diff_normalized_2[obs];
5119 }
5120 }
5121 const MFloat Ampl_aver_Diff = 100 * sum_max_Ampl_Diff_normalized / globalNoObservers(); // [%]
5122 const MFloat Freq_aver_Diff = 100 * sum_freq_Diff_normalized / globalNoObservers(); // [%]
5123
5124 // In case of uneven and even periods comparison
5125 MFloat Side_Lobes = 0.0;
5126 MFloat Freq_aver_Diff_UnevenPeriods = 0.0;
5127 MFloat Ampl_aver_Diff_UnevenPeriods = 0.0;
5128 if(noSamples() == EP_Samples) {
5129 Ampl_aver_Diff_UnevenPeriods = 100 * sum_max_Ampl_Diff_normalized_2 / globalNoObservers(); //[%]
5130 Freq_aver_Diff_UnevenPeriods = 100 * sum_freq_Diff_normalized_2 / globalNoObservers(); //[%]
5131 Side_Lobes = 100 * sum_SideLobes / globalNoObservers(); //[%]
5132 // Those minor deviations are due to the writing out in a file and re-reading it again
5133 // (Analytic Even Periods). Thus, they match perfectly but a deviation is falsly shown
5134 if(Freq_aver_Diff_UnevenPeriods < 1e-10) {
5135 Freq_aver_Diff_UnevenPeriods = 0.0;
5136 }
5137 }
5138
5139 // OUTPUT OF max Amplitude at given frequencie
5140 MString fileFreq = "./FreqComparison/maxFreqCompare_";
5141 fileFreq += std::to_string(noSegs);
5142 fileFreq += ".dat";
5143 std::ofstream outfile;
5144 outfile.open(fileFreq);
5145 outfile << Y << " " << Freq_aver_Diff << " " << Ampl_aver_Diff << " " << Side_Lobes << std::endl;
5146 outfile.close();
5147
5148 // Freq for samples
5149 MString fileF = "./FreqComparison/maxFreqCompareSamples_";
5150 fileF += std::to_string(noSamples());
5151 fileF += ".dat";
5152 outfile.open(fileF);
5153 outfile << R_min << " " << Freq_aver_Diff << " " << Ampl_aver_Diff << " " << Side_Lobes << std::endl;
5154
5155 outfile.close();
5156
5157 if(noSamples() == EP_Samples) {
5158 // Freq for periods
5159 MString fileFr = "./FreqComparison/maxFreqComparePeriods_";
5160 fileFr += std::to_string(noPeriods);
5161 fileFr += ".dat";
5162 outfile.open(fileFr);
5163 outfile << noPeriods << " " << Freq_aver_Diff_UnevenPeriods << " " << Ampl_aver_Diff_UnevenPeriods << " "
5164 << Side_Lobes << " " << Ampl_aver_Diff << std::endl;
5165 outfile.close();
5166 }
5167
5168 // OUTPUT IF ONE VARIES NUMBER OF SEGMENTS
5169 MString ofile = "./Diff_seg/accuracy_";
5170 ofile += std::to_string(noSegs);
5171 ofile += ".dat";
5172 outfile.open(ofile);
5173 outfile << Y << " " << Q_C << " " << max_diff << " " << Z << " " << Side_Lobes << std::endl;
5174 outfile.close();
5175
5176 // OUTPUT IF ONE VARIES NUMBER OF SAMPLES
5177 MString fileName = "./Diff_Samples/accuracy_";
5178 fileName += std::to_string(noSamples());
5179 fileName += ".dat";
5180 outfile.open(fileName);
5181 outfile << R_min << " " << Q_C << " " << max_diff << " " << Side_Lobes << std::endl;
5182 outfile.close();
5183
5184 if(noSamples() == EP_Samples) {
5185 // OUTPUT IF ONE VARIES NUMBER OF SAMPLES
5186 MString fileN = "./Diff_Periods/accuracy_";
5187 fileN += std::to_string(noPeriods);
5188 fileN += ".dat";
5189 outfile.open(fileN);
5190 outfile << noPeriods << " " << Q_C_UnevenPeriods << " " << max_diff_2 << " " << Side_Lobes << std::endl;
5191 outfile.close();
5192 }
5193
5194 // OUTPUT IF ONE VARIES NUMBER OF SAMPLES
5195 MString fileNa = "./Diff_omega/accuracy_";
5196 fileNa += std::to_string(omega_factor);
5197 fileNa += ".dat";
5198 outfile.open(fileNa);
5199 outfile << omega_factor << " " << Q_C << " " << max_diff << " " << Side_Lobes << std::endl;
5200 outfile.close();
5201
5202 std::cout << "8. Accuarcy calculation finished." << std::endl;
5203 }
5204}
5205
5206
5207template class AcaSolver<2>;
5208template class AcaSolver<3>;
ACA post processing class for overall sound pressure calculation.
ACA post processing class for absoulte pressure calculation.
ACA post processing class for calculation of root mean square pressure.
ACA post processing class for sound pressure level calculation.
Acoustic extrapolation of near/near-far field data to the true far-field using an integral method (cu...
Definition: acasolver.h:78
AcaSolver(const MInt solverId, const MPI_Comm comm)
Definition: acasolver.cpp:24
void transformCoordinatesToFlowDirection3D(MFloat *const fX, MFloat *const fY, MFloat *const fZ)
Definition: acasolver.cpp:2006
void readNoSegmentsAndSamples(const MInt surfaceId, MInt &noSegments, MInt &noSamples, MString &inputFileName)
Read the number of segments and samples for the given surface id.
Definition: acasolver.cpp:2994
void generateSurfaceData()
Generation of analytical input data.
Definition: acasolver.cpp:3924
void loadObserverSignals()
Load the observer signals from a file for further postprocessing.
Definition: acasolver.cpp:2477
void applySymmetryBc(const std::array< MFloat, nDim > coord, MFloat *const p_complexVariables)
Symmetry boundary condition.
Definition: acasolver.cpp:4547
void initConversionFactors()
Initialize the conversion factors required to convert between.
Definition: acasolver.cpp:4721
void interpolateFromSourceToObserverTime(const MFloat distMinObservers, MFloat *const p_perturbedFWH)
Interpolate the observer pressure signal from source time to observer time using the advanced time ap...
Definition: acasolver.cpp:2123
void storeSurfaceData(const MInt surfaceId)
Store the surface data. Note: used for validating the data input and for the output of generated data...
Definition: acasolver.cpp:3428
void calcObsPressureAnalytic()
Definition: acasolver.cpp:2583
void genQuadrupoleAnalytic2D(const MFloat *coord, const SourceParameters &param, SourceVars &vars)
Quadrupole.
Definition: acasolver.cpp:4307
void combineSurfaceIntegralsInTime(const MInt globalObserverId, MFloat *const p_perturbedFWH)
Combine the FWH surface integrals of the time domain method of all ranks.
Definition: acasolver.cpp:2253
void genDipoleAnalytic2D(const MFloat *coord, const SourceParameters &param, SourceVars &vars)
Dipole.
Definition: acasolver.cpp:4160
void checkNoSamplesPotencyOfTwo()
Definition: acasolver.cpp:4864
void changeDimensionsObserverData()
Definition: acasolver.cpp:4816
void calcSurfaceIntegralsAtSourceTime(const MInt globalObserverId)
Calculate the FWH surface integrals for a certain observer using the time-domain formulation.
Definition: acasolver.cpp:1866
MFloat calcTimeDerivative(const MInt segmentId, const MInt varId, const MInt tau)
Definition: acasolver.cpp:2048
void transformBackToOriginalCoordinate2D(MFloat *const fX, MFloat *const fY)
Definition: acasolver.cpp:2020
void genQuadrupoleAnalytic3D(const MFloat *coord, const SourceParameters &param, SourceVars &vars)
3D quadrupole analytic
Definition: acasolver.cpp:4409
void setNumericalProperties()
Read/set all numerical properties.
Definition: acasolver.cpp:421
void generateSurfaceCircle(const MInt sId)
Generate a circular surface in 2D.
Definition: acasolver.cpp:3853
void distributeObservers()
Distribute observers on all ranks.
Definition: acasolver.cpp:2333
void calcSourceTermsFwhTime(const MInt segmentId)
Calculate FWH source terms for the time domain FWH formulation Reference: [1] Brentner,...
Definition: acasolver.cpp:1062
void genDipoleAnalytic3D(const MFloat *coord, const SourceParameters &param, SourceVars &vars)
3D dipole analytic
Definition: acasolver.cpp:4233
void postprocessObserverSignals()
Postprocessing of observer signals.
Definition: acasolver.cpp:2531
void calcFrequencies()
Calculate the frequencies.
Definition: acasolver.cpp:1549
void genMonopoleAnalytic3D(const MFloat *coord, const SourceParameters &param, SourceVars &vars)
3D analytic monopole generation
Definition: acasolver.cpp:4097
void calcSurfaceIntegralsForObserver(const std::array< MFloat, nDim > coord, MFloat *const p_complexVariables)
Calculate the FWH surface integrals for a certain observer location.
Definition: acasolver.cpp:1573
MBool solutionStep() override
Main compute method to perform acoustic far-field prediction.
Definition: acasolver.cpp:807
void calculateObserverInFrequency()
Calculate the observer signals in frequency domain.
Definition: acasolver.cpp:2294
void windowAndTransformSources()
Apply window function to source terms and transform into frequency domain.
Definition: acasolver.cpp:1253
void generateSurfacePlane(const MInt sId)
Generate a surface plane (Cartesian).
Definition: acasolver.cpp:3581
void DiscreteFourierTransform(MFloat *dataArray, const MInt size, const MInt dir, MFloat *result, const MBool inPlace)
Discrete Fourier transform (computational expensive O(n^2) instead of O(n*logn) compared to FFT)
Definition: acasolver.cpp:1500
void generateSurfaces()
Generation of surfaces.
Definition: acasolver.cpp:3493
void saveObserverSignals()
Save the observer signals. Save observer data: 1. pressure data in time domain and 2....
Definition: acasolver.cpp:2388
void calcSourceTerms()
Main compute functions.
Definition: acasolver.cpp:879
void genMonopoleAnalytic2D(const MFloat *coord, const SourceParameters &param, SourceVars &vars)
Monopole.
Definition: acasolver.cpp:4035
void changeDimensionsSurfaceData()
Change of the non-dimensionalization of input data from the stagnation state (0) non-dim....
Definition: acasolver.cpp:4784
void calcMinDistanceForObservers(MFloat *const distMinObservers)
Find the minimum distance from the surface elements to the observers.
Definition: acasolver.cpp:2069
void readSurfaceData()
Read the surface data (or generate for an analytical case)
Definition: acasolver.cpp:2925
void readSurfaceDataFromFile(const MInt surfaceId)
Read the data for one surface.
Definition: acasolver.cpp:3064
MInt readNoSegmentsSurfaceGeneration(const MInt sId, std::vector< MInt > &noSegments, const MInt lengthCheck=-1)
Determine the number of segments to be generated for a surface, returns the total number of segments ...
Definition: acasolver.cpp:3541
void initTimers()
Initialize solver timers.
Definition: acasolver.cpp:41
void initParallelization()
Initialize parallelization (distribution of surface segments on all domains)
Definition: acasolver.cpp:2672
void initSolver() override
Initialize solver.
Definition: acasolver.cpp:28
void averageTimer()
Average all FWH timer over participating ranks.
Definition: acasolver.cpp:74
void initObserverPoints()
Initialize observer points.
Definition: acasolver.cpp:4572
void genVortexConvectionAnalytic2D(const MFloat *obsCoord, const SourceParameters &m_sourceParameters, SourceVars &sourceVars)
Inviscid vortex convection.
Definition: acasolver.cpp:4506
void backtransformObservers()
Backtransform the observer frequency signals into the time domain.
Definition: acasolver.cpp:2348
void windowAndTransformObservers()
Apply window function to the observer time series data and transform it into frequency domain.
Definition: acasolver.cpp:1354
void transformCoordinatesToFlowDirection2D(MFloat *const fX, MFloat *const fY)
Coordinate transformation.
Definition: acasolver.cpp:1994
void calcWindowFactors(MFloat *const p_window)
Compute the window function factors and the normalization factor.
Definition: acasolver.cpp:1195
void setInputOutputProperties()
Read/set all I/O related properties.
Definition: acasolver.cpp:107
void FastFourierTransform(MFloat *dataArray, const MInt size, const MInt dir, MFloat *result, const MBool inPlace)
Functions for Fourier Transformation.
Definition: acasolver.cpp:1411
void calculateObserverInTime()
Calculate the observer signals in time domain.
Definition: acasolver.cpp:2309
MString getSurfaceDataFileName(const MInt surfaceId, const MInt fileNo)
IO methods.
Definition: acasolver.cpp:2913
void transformBackToOriginalCoordinate3D(MFloat *const fX, MFloat *const fY, MFloat *const fZ)
Definition: acasolver.cpp:2033
void combineSurfaceIntegrals(const MInt globalObserverId, MFloat *const p_complexVariables)
Combine the FWH surface integrals of all ranks.
Definition: acasolver.cpp:2203
void calcSourceTermsFwhFreq(const MInt segmentId)
Source term compute kernels.
Definition: acasolver.cpp:909
void computeAccuracy()
Definition: acasolver.cpp:4892
void computeDt()
Definition: acasolver.cpp:4845
static MInt propertyLength(const MString &name, MInt solverId=m_noSolvers)
Returns the number of elements of a property.
Definition: context.cpp:538
static void assertPropertyLength(const MString &name, const MInt length, const MInt solverId=m_noSolvers)
Assert that the length of a property matches the given length.
Definition: context.cpp:559
static MBool propertyExists(const MString &name, MInt solver=m_noSolvers)
This function checks if a property exists in general.
Definition: context.cpp:494
This class is a ScratchSpace.
Definition: scratch.h:758
pointer data()
Definition: scratch.h:289
void fill(T val)
fill the scratch with a given value
Definition: scratch.h:311
Parent class of all solvers This class is the base for all solvers. I.e. all solver class (e....
Definition: solver.h:29
MInt string2enum(MString theString)
This global function translates strings in their corresponding enum values (integer values)....
Definition: enums.cpp:20
@ FWH_METHOD
Definition: enums.h:378
@ FWH_APE_METHOD
Definition: enums.h:380
@ MAIA_FWH_TIME
Definition: enums.h:102
@ MAIA_FWH_FREQUENCY
Definition: enums.h:101
MInt loadPointCoordinatesFromFile(const MString inputFileName, const MInt nDim, std::vector< MFloat > &coordinates)
Loads point coordinates from an input file.
Definition: functions.cpp:182
constexpr Real POW2(const Real x)
Definition: functions.h:119
MBool approx(const T &, const U &, const T)
Definition: functions.h:272
InfoOutFile m_log
void SWAP(T &a, T &b)
Definition: kdtree.h:21
int32_t MInt
Definition: maiatypes.h:62
std::basic_string< char > MString
Definition: maiatypes.h:55
double MFloat
Definition: maiatypes.h:52
bool MBool
Definition: maiatypes.h:58
MInt id
Definition: maiatypes.h:71
uint64_t MUlong
Definition: maiatypes.h:65
int MPI_Scatterv(const void *sendbuf, const int sendcount[], const int displs[], MPI_Datatype sendtype, void *recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm, const MString &name, const MString &sndvarname, const MString &rcvvarname)
same as MPI_Scatterv
int MPI_Comm_create(MPI_Comm comm, MPI_Group group, MPI_Comm *newcomm, const MString &name, const MString &varname)
same as MPI_Comm_create, but updates the number of MPI communicators
int MPI_Group_incl(MPI_Group group, int n, const int ranks[], MPI_Group *newgroup, const MString &name)
same as MPI_Group_incl
int MPI_Comm_group(MPI_Comm comm, MPI_Group *group, const MString &name, const MString &varname)
same as MPI_Comm_group
int MPI_Reduce(const void *sendbuf, void *recvbuf, int count, MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm, const MString &name, const MString &sndvarname, const MString &rcvvarname)
same as MPI_Reduce
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_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_Group_free(MPI_Group *group, const MString &name)
same as MPI_Group_free
T cos(const T a, const T b, const T x)
Cosine slope filter.
Definition: filter.h:125
Namespace for auxiliary functions/classes.
PARALLELIO_DEFAULT_BACKEND ParallelIo
Definition: parallelio.h:292
MFloat dist(const Point< DIM > &p, const Point< DIM > &q)
Definition: pointbox.h:54
Hold data for symmetry boundary condition.
Definition: acasolver.h:554
Parameter for analytical acoustic source terms.
Definition: acasolver.h:50
define array structures