ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_CloudChamberBudget.H
Go to the documentation of this file.
1 #ifndef ERF_CLOUD_CHAMBER_BUDGET_H_
2 #define ERF_CLOUD_CHAMBER_BUDGET_H_
3 
4 #include <AMReX_Geometry.H>
5 #include <AMReX_MultiFab.H>
6 #include <AMReX_ParallelDescriptor.H>
7 #include <ERF_IndexDefines.H>
8 
9 #include <array>
10 #include <algorithm>
11 #include <cmath>
12 #include <fstream>
13 #include <iomanip>
14 #include <limits>
15 #include <string>
16 
17 /**
18  * Low-overhead Stage 1 conserved-scalar budget accumulator.
19  *
20  * Fluxes are positive in the positive coordinate direction. The reported
21  * boundary balance is therefore low-minus-high for each coordinate pair.
22  * Local face sums are held until a configured report interval, then one
23  * packed MPI reduction produces the six-face values. For the anelastic RK2
24  * path, nrk=0 is retained and nrk=1 applies the trapezoidal stage weight;
25  * this avoids counting both RHS evaluations as two physical timesteps.
26  */
28 {
29 public:
30  enum Scalar : int { RhoTheta = 0, RhoQv = 1, RhoQc = 2, NumScalars = 3 };
31  static constexpr int NumFaces = 2 * AMREX_SPACEDIM;
32 
33  explicit CloudChamberBudget (int interval) : m_interval(interval) {}
34 
35  bool enabled () const noexcept { return m_interval > 0; }
36  /**
37  * Determine if a budget report is due for the given timestep.
38  *
39  * @param step Current timestep
40  * @return True if a report is due, false otherwise
41  */
42  bool due (int step) const noexcept {
43  return enabled() && step > 0 && (step % m_interval == 0);
44  }
45 
46  /**
47  * Initialize the base state for the budget accumulation period.
48  *
49  * @param cons Conserved variable MultiFab
50  * @param geom Geometry used for volume calculation
51  * @param step Timestep at the start of the interval
52  * @param time Simulation time at the start of the interval
53  */
54  void set_initial_state (const amrex::MultiFab& cons,
55  const amrex::Geometry& geom,
56  int step = 0,
57  double time = 0.0)
58  {
59  if (m_have_initial) { return; }
60  const amrex::Real volume = geom.CellSize(0) * geom.CellSize(1) * geom.CellSize(2);
61  for (int s = 0; s < NumScalars; ++s) {
62  const int comp = (s == RhoTheta) ? RhoTheta_comp : RhoQ1_comp + (s-1);
63  m_initial_state[s] = (comp < cons.nComp()) ? cons.sum(comp, true) * volume : amrex::Real(0.0);
64  }
65  m_interval_start_step = step;
66  m_interval_start_time = time;
67  m_have_initial = true;
68  }
69 
70  /**
71  * Integrate boundary fluxes for a specific Runge-Kutta stage.
72  *
73  * @param scalar Index of the scalar being tracked
74  * @param nrk Runge-Kutta stage index
75  * @param dt Timestep size
76  * @param xflux Fluxes in the x-direction
77  * @param yflux Fluxes in the y-direction
78  * @param zflux Fluxes in the z-direction
79  * @param geom Geometry used for face area calculation
80  * @param flux_comp Component index of the flux
81  */
82  void capture_stage (int scalar, int nrk, amrex::Real dt,
83  const amrex::MultiFab& xflux,
84  const amrex::MultiFab& yflux,
85  const amrex::MultiFab& zflux,
86  const amrex::Geometry& geom,
87  int flux_comp = 0)
88  {
89  if (!enabled() || scalar < 0 || scalar >= NumScalars) { return; }
90  std::array<amrex::Real, NumFaces> current{};
91  for (int d = 0; d < AMREX_SPACEDIM; ++d) {
92  amrex::Box low = geom.Domain();
93  amrex::Box high = low;
94  low.surroundingNodes(d);
95  high.surroundingNodes(d);
96  low.setSmall(d, geom.Domain().smallEnd(d));
97  low.setBig(d, geom.Domain().smallEnd(d));
98  high.setSmall(d, geom.Domain().bigEnd(d)+1);
99  high.setBig(d, geom.Domain().bigEnd(d)+1);
100  const amrex::MultiFab* flux = (d == 0) ? &xflux : ((d == 1) ? &yflux : &zflux);
101  const amrex::Real area = (d == 0) ? geom.CellSize(1)*geom.CellSize(2) :
102  ((d == 1) ? geom.CellSize(0)*geom.CellSize(2) :
103  geom.CellSize(0)*geom.CellSize(1));
104  current[2*d] = flux->sum(low, flux_comp, true) * area * dt;
105  current[2*d+1] = flux->sum(high, flux_comp, true) * area * dt;
106  }
107 
108  if (nrk == 0) {
109  m_stage0[scalar] = current;
110  m_have_stage0[scalar] = true;
111  } else if (nrk == 1 && m_have_stage0[scalar]) {
112  for (int face = 0; face < NumFaces; ++face) {
113  m_cumulative[scalar][face] +=
114  amrex::Real(0.5) * (m_stage0[scalar][face] + current[face]);
115  }
116  m_have_stage0[scalar] = false;
117  }
118  }
119 
120  std::array<amrex::Real, NumScalars>
121  state_integrals (const amrex::MultiFab& cons,
122  const amrex::Geometry& geom) const
123  {
124  const amrex::Real volume = geom.CellSize(0) * geom.CellSize(1) * geom.CellSize(2);
125  std::array<amrex::Real, NumScalars> result{};
126  result[RhoTheta] = cons.sum(RhoTheta_comp, true) * volume;
127  result[RhoQv] = (RhoQ1_comp < cons.nComp()) ? cons.sum(RhoQ1_comp, true) * volume : amrex::Real(0.0);
128  result[RhoQc] = (RhoQ2_comp < cons.nComp()) ? cons.sum(RhoQ2_comp, true) * volume : amrex::Real(0.0);
129  return result;
130  }
131 
132  /**
133  * Accumulate internal source terms for the tracked scalars.
134  *
135  * @param before State of scalars before source application
136  * @param after State of scalars after source application
137  */
138  void record_internal_source (const std::array<amrex::Real, NumScalars>& before,
139  const std::array<amrex::Real, NumScalars>& after)
140  {
141  for (int s = 0; s < NumScalars; ++s) {
142  m_internal_source[s] += after[s] - before[s];
143  }
144  }
145 
146  /**
147  * Perform global reduction and write the budget report to file.
148  *
149  * @param step Current timestep
150  * @param time Current simulation time
151  * @param cons Current conserved variable MultiFab
152  * @param geom Geometry used for state integration
153  * @param cloudy Flag indicating if the state is cloudy
154  */
155  void report (int step, double time, const amrex::MultiFab& cons,
156  const amrex::Geometry& geom, bool cloudy)
157  {
158  if (!due(step) || !m_have_initial) { return; }
159  const auto current = state_integrals(cons, geom);
160  std::array<amrex::Real, NumScalars*NumFaces + NumScalars*3> packed{};
161  int index = 0;
162  for (int s = 0; s < NumScalars; ++s) {
163  for (int f = 0; f < NumFaces; ++f) { packed[index++] = m_cumulative[s][f]; }
164  }
165  for (int s = 0; s < NumScalars; ++s) { packed[index++] = m_initial_state[s]; }
166  for (int s = 0; s < NumScalars; ++s) { packed[index++] = current[s]; }
167  for (int s = 0; s < NumScalars; ++s) { packed[index++] = m_internal_source[s]; }
168  amrex::ParallelDescriptor::ReduceRealSum(packed.data(), static_cast<int>(packed.size()));
169 
170  std::array<std::array<amrex::Real, NumFaces>, NumScalars> global_faces{};
171  index = 0;
172  for (int s = 0; s < NumScalars; ++s) {
173  for (int f = 0; f < NumFaces; ++f) { global_faces[s][f] = packed[index++]; }
174  }
175  std::array<amrex::Real, NumScalars> initial{};
176  std::array<amrex::Real, NumScalars> global_current{};
177  index = NumScalars*NumFaces;
178  for (int s = 0; s < NumScalars; ++s) { initial[s] = packed[index++]; }
179  for (int s = 0; s < NumScalars; ++s) { global_current[s] = packed[index++]; }
180  std::array<amrex::Real, NumScalars> global_internal{};
181  for (int s = 0; s < NumScalars; ++s) { global_internal[s] = packed[index++]; }
182 
183  if (amrex::ParallelDescriptor::IOProcessor()) {
184  std::ofstream out("cloud_chamber_budget.dat", std::ios::app);
185  if (out.tellp() == std::streampos(0)) {
186  out << "# start_step start_time end_step end_time scalar units "
187  "xlo xhi ylo yhi zlo zhi net_boundary volume_change "
188  "internal_source residual tolerance status\n";
189  }
190  out << std::setprecision(17);
191  const char* names[NumScalars] = {"rhoTheta", "water_vapor", "cloud_water"};
192  const char* units[NumScalars] = {"conserved_scalar", "mixing_ratio_density", "mixing_ratio_density"};
193  for (int s = 0; s < NumScalars; ++s) {
194  std::array<amrex::Real, NumFaces> faces = global_faces[s];
195  amrex::Real net = faces[0] - faces[1] + faces[2] - faces[3] + faces[4] - faces[5];
196  const amrex::Real change = global_current[s] - initial[s];
197  const amrex::Real internal = global_internal[s];
198  const amrex::Real residual = change - net - internal;
199  const amrex::Real scale = std::max({amrex::Real(1.0),
200  std::abs(initial[s]), std::abs(global_current[s]),
201  std::abs(net), std::abs(internal)});
202  const amrex::Real effective_operations =
203  static_cast<amrex::Real>(geom.Domain().numPts()) +
204  static_cast<amrex::Real>(NumFaces);
205  amrex::Real boundary_scale = amrex::Real(0.0);
206  for (const auto value : faces) { boundary_scale += std::abs(value); }
207  const amrex::Real tolerance = amrex::Real(128.0) *
209  (effective_operations * scale + boundary_scale + std::abs(internal));
210  const bool status = std::abs(residual) <= tolerance;
211  const char* status_text = status ? "PASS" :
212  (cloudy && s == RhoTheta ? "UNSUPPORTED_SOURCE" : "FAIL");
213  out << m_interval_start_step << ' ' << m_interval_start_time << ' '
214  << step << ' ' << time << ' ' << names[s] << ' ' << units[s];
215  for (const auto value : faces) { out << ' ' << value; }
216  out << ' ' << net << ' ' << change << ' ' << internal << ' '
217  << residual << ' ' << tolerance << ' ' << status_text << '\n';
218  }
219  std::array<amrex::Real, NumFaces> total_faces{};
220  for (int f = 0; f < NumFaces; ++f) {
221  total_faces[f] = global_faces[RhoQv][f] + global_faces[RhoQc][f];
222  }
223  const amrex::Real total_net = total_faces[0] - total_faces[1] +
224  total_faces[2] - total_faces[3] + total_faces[4] - total_faces[5];
225  const amrex::Real total_change = global_current[RhoQv] + global_current[RhoQc] -
226  initial[RhoQv] - initial[RhoQc];
227  const amrex::Real total_internal = global_internal[RhoQv] + global_internal[RhoQc];
228  const amrex::Real total_residual = total_change - total_net - total_internal;
229  const amrex::Real total_scale = std::max({amrex::Real(1.0), std::abs(total_change),
230  std::abs(total_net), std::abs(total_internal)});
231  amrex::Real total_boundary_scale = amrex::Real(0.0);
232  for (const auto value : total_faces) { total_boundary_scale += std::abs(value); }
233  const amrex::Real total_tolerance = amrex::Real(128.0) *
235  (static_cast<amrex::Real>(geom.Domain().numPts() + NumFaces) * total_scale +
236  total_boundary_scale + std::abs(total_internal));
237  const bool total_status = std::abs(total_residual) <= total_tolerance;
238  out << m_interval_start_step << ' ' << m_interval_start_time << ' '
239  << step << ' ' << time << " total_nonprecipitating_water mixing_ratio_density";
240  for (const auto value : total_faces) { out << ' ' << value; }
241  out << ' ' << total_net << ' ' << total_change << ' ' << total_internal << ' '
242  << total_residual << ' ' << total_tolerance << ' '
243  << (total_status ? "PASS" : "FAIL") << '\n';
244  }
245  // The next report is an interval-local balance beginning at the
246  // state just reported. Keep this reference rank-local: assigning the
247  // MPI-global value here would make every rank contribute the same
248  // global reference at the next reduction.
249  m_initial_state = current;
250  m_cumulative = {};
251  m_internal_source = {};
252  m_stage0 = {};
253  m_have_stage0 = {};
254  m_interval_start_step = step;
255  m_interval_start_time = time;
256  }
257 
258 private:
259  int m_interval = 0;
261  double m_interval_start_time = 0.0;
262  bool m_have_initial = false;
263  std::array<bool, NumScalars> m_have_stage0{};
264  std::array<amrex::Real, NumScalars> m_initial_state{};
265  std::array<amrex::Real, NumScalars> m_internal_source{};
266  std::array<std::array<amrex::Real, NumFaces>, NumScalars> m_stage0{};
267  std::array<std::array<amrex::Real, NumFaces>, NumScalars> m_cumulative{};
268 };
269 
270 #endif
#define RhoTheta_comp
Definition: ERF_IndexDefines.H:40
#define RhoQ2_comp
Definition: ERF_IndexDefines.H:46
#define RhoQ1_comp
Definition: ERF_IndexDefines.H:45
std::string units
Definition: ERF_Plotfile2DCatalog.cpp:103
amrex::Real Real
Definition: ERF_ShocInterface.H:19
Definition: ERF_CloudChamberBudget.H:28
bool enabled() const noexcept
Definition: ERF_CloudChamberBudget.H:35
void report(int step, double time, const amrex::MultiFab &cons, const amrex::Geometry &geom, bool cloudy)
Definition: ERF_CloudChamberBudget.H:155
std::array< std::array< amrex::Real, NumFaces >, NumScalars > m_stage0
Definition: ERF_CloudChamberBudget.H:266
std::array< amrex::Real, NumScalars > m_initial_state
Definition: ERF_CloudChamberBudget.H:264
double m_interval_start_time
Definition: ERF_CloudChamberBudget.H:261
std::array< amrex::Real, NumScalars > state_integrals(const amrex::MultiFab &cons, const amrex::Geometry &geom) const
Definition: ERF_CloudChamberBudget.H:121
void set_initial_state(const amrex::MultiFab &cons, const amrex::Geometry &geom, int step=0, double time=0.0)
Definition: ERF_CloudChamberBudget.H:54
CloudChamberBudget(int interval)
Definition: ERF_CloudChamberBudget.H:33
bool m_have_initial
Definition: ERF_CloudChamberBudget.H:262
void capture_stage(int scalar, int nrk, amrex::Real dt, const amrex::MultiFab &xflux, const amrex::MultiFab &yflux, const amrex::MultiFab &zflux, const amrex::Geometry &geom, int flux_comp=0)
Definition: ERF_CloudChamberBudget.H:82
Scalar
Definition: ERF_CloudChamberBudget.H:30
@ RhoTheta
Definition: ERF_CloudChamberBudget.H:30
@ RhoQv
Definition: ERF_CloudChamberBudget.H:30
@ NumScalars
Definition: ERF_CloudChamberBudget.H:30
@ RhoQc
Definition: ERF_CloudChamberBudget.H:30
std::array< bool, NumScalars > m_have_stage0
Definition: ERF_CloudChamberBudget.H:263
int m_interval
Definition: ERF_CloudChamberBudget.H:259
std::array< amrex::Real, NumScalars > m_internal_source
Definition: ERF_CloudChamberBudget.H:265
static constexpr int NumFaces
Definition: ERF_CloudChamberBudget.H:31
std::array< std::array< amrex::Real, NumFaces >, NumScalars > m_cumulative
Definition: ERF_CloudChamberBudget.H:267
int m_interval_start_step
Definition: ERF_CloudChamberBudget.H:260
bool due(int step) const noexcept
Definition: ERF_CloudChamberBudget.H:42
void record_internal_source(const std::array< amrex::Real, NumScalars > &before, const std::array< amrex::Real, NumScalars > &after)
Definition: ERF_CloudChamberBudget.H:138
@ cons
Definition: ERF_IndexDefines.H:214
real(c_double), parameter epsilon
Definition: ERF_module_model_constants.F90:12