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