ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_SatAdj.H
Go to the documentation of this file.
1 #ifndef ERF_SATADJ_H
2 #define ERF_SATADJ_H
3 
4 /*
5  * SatAdj is a cell-local, fixed-pressure saturation-adjustment source step.
6  * It is not a flux-form finite-volume update.
7  *
8  * The scheme reads rho, rhoTheta, rhoQv, and rhoQc from the conserved state,
9  * adjusts theta/qv/qc locally, and writes rhoTheta/rhoQv/rhoQc back.
10  * Density is copied for diagnostics and conservative reconstruction, but rho is
11  * not a SatAdj prognostic variable and is not modified by this module.
12  */
13 
14 #include <string>
15 #include <vector>
16 #include <memory>
17 #include <limits>
18 
19 #include <AMReX_FArrayBox.H>
20 #include <AMReX_Geometry.H>
21 #include <AMReX_MultiFabUtil.H>
22 #include <AMReX_GpuContainers.H>
23 
24 #include "ERF_EOS.H"
25 #include "ERF_NumericalConstants.H"
27 #include "ERF_MicrophysicsUtils.H"
28 #include "ERF_IndexDefines.H"
29 #include "ERF_DataStruct.H"
30 #include "ERF_NullMoist.H"
31 #include "ERF_TileNoZ.H"
32 
33 namespace MicVar_SatAdj {
34  enum {
35  // independent variables
36  rho=0, // density copied from conserved state; not modified by SatAdj
37  theta, // dry potential temperature
38  tabs, // absolute temperature [K]
39  pres, // pressure [mbar/hPa] for saturation helpers
40  // non-precipitating vars
41  qv, // water-vapor mixing ratio
42  qc, // cloud-water mixing ratio
43  NumVars
44  };
45 }
46 
47 class SatAdj : public NullMoist {
48 
49  using FabPtr = std::shared_ptr<amrex::MultiFab>;
50 
51 public:
52  // constructor
53  SatAdj () {}
54 
55  // destructor
56  virtual ~SatAdj () = default;
57 
58  // cloud physics
59  void AdvanceSatAdj (const SolverChoice& /*solverChoice*/);
60 
61  // Set up for first time
62  void
63  Define (SolverChoice& sc) override
64  {
65  m_fac_cond = lcond / sc.c_p;
66  m_rdOcp = sc.rdOcp;
67  m_do_cond = (!sc.uses_shoc_family());
69  }
70 
71  // init
72  void
73  Init (const amrex::MultiFab& cons_in,
74  const amrex::BoxArray& /*grids*/,
75  const amrex::Geometry& geom,
76  const amrex::Real& dt_advance,
77  std::unique_ptr<amrex::MultiFab>& /*z_phys_nd*/,
78  std::unique_ptr<amrex::MultiFab>& /*detJ_cc*/) override;
79 
80  // Copy state into micro vars
81  void
82  Copy_State_to_Micro (const amrex::MultiFab& cons_in) override;
83 
84  void
85  Update_Micro_Vars (amrex::MultiFab& cons_in,
86  const amrex::MultiFab* base_state) override;
87 
88  // Copy state into micro vars
89  void
90  Copy_Micro_to_State (amrex::MultiFab& cons_in) override;
91 
92  // update micro vars
93  void
94  Update_Micro_Vars (amrex::MultiFab& cons_in) override
95  {
96  if (!m_do_cond) { return; }
97  this->Copy_State_to_Micro(cons_in);
98  }
99 
100  // update state vars
101  void
102  Update_State_Vars (amrex::MultiFab& cons_in,
103  const amrex::MultiFab& /*z_phys_nd*/) override
104  {
105  if (!m_do_cond) { return; }
106  this->Copy_Micro_to_State(cons_in);
107  }
108 
109  // wrapper to advance micro vars
110  void
111  Advance (const amrex::Real& dt_advance,
112  const SolverChoice& solverChoice) override
113  {
114  dt = dt_advance;
115 
116  this->AdvanceSatAdj(solverChoice);
117  }
118 
119  amrex::MultiFab*
120  Qmoist_Ptr (const int& varIdx) override
121  {
123  return nullptr;
124  }
125 
126  int
127  Qmoist_Size () override { return SatAdj::m_qmoist_size; }
128 
129  int
131 
132  int
134 
135  void
137  std::vector<int>& a_idx,
138  std::vector<std::string>& a_names) const override
139  {
140  a_idx.clear();
141  a_names.clear();
142  }
143 
144  AMREX_GPU_HOST_DEVICE
145  AMREX_FORCE_INLINE
146  static amrex::Real
147  // Newton solve for the saturated final temperature at fixed pressure.
148  // The invariant is T + (L/cp) * qv for the local cell. The solve finds
149  // T_new such that qv_new = qsat(T_new, p). The pressure argument is held
150  // fixed in mbar/hPa throughout the solve.
151  //
152  // Solves the fixed-pressure saturation equation
153  // 0 = -T_new + T_old + (L/cp) * (qv_old - qsat(T_new, p))
154  // for T_new. On return, qsat is evaluated exactly at the returned
155  // temperature. The caller uses that qsat to set qv and qc while
156  // conserving qv + qc. Density does not enter this solve.
157  // If the Newton iteration reaches max_niter before the tolerance, the
158  // routine returns the last iterate and recomputes qsat at that temperature.
160  const amrex::Real tabs_old,
161  const amrex::Real pres_mbar,
162  const amrex::Real qv_old,
163  amrex::Real& qsat)
164  {
165 #ifdef AMREX_USE_FLOAT
166  constexpr amrex::Real tol = amrex::Real(1.e-4);
167 #else
168  constexpr amrex::Real tol = amrex::Real(1.e-8);
169 #endif
170  constexpr int max_iter = 20;
171 
172  int niter = 0;
173  amrex::Real F, dFdT, dqsat;
174  amrex::Real tabs = tabs_old;
175 
176  //==================================================
177  // Newton iteration to qv=qsat (cloud phase only)
178  //==================================================
179  // Saturation moisture fractions
180  erf_qsatw(tabs, pres_mbar, qsat);
181  erf_dtqsatw(tabs, pres_mbar, dqsat);
182 
183  // Function for root finding:
184  // 0 = -T_new + T_old + L_eff/C_p * (qv - qsat)
185  F = -tabs + tabs_old + fac_cond*(qv_old - qsat);
186 
187  // Iterate when necessary
188  while (std::abs(F) > tol && niter < max_iter) {
189  // Derivative of function (T_new iterated on)
190  dFdT = -one - fac_cond*dqsat;
191 
192  // Update the temperature
193  tabs -= F/dFdT;
194 
195  // Saturation moisture fractions
196  erf_qsatw(tabs, pres_mbar, qsat);
197  erf_dtqsatw(tabs, pres_mbar, dqsat);
198 
199  // Function for root finding:
200  // 0 = -T_new + T_old + L_eff/C_p * (qv - qsat)
201  F = -tabs + tabs_old + fac_cond*(qv_old - qsat);
202 
203  // Update iteration
204  ++niter;
205  }
206 
207  // NOTE: the while loop already enforces the iteration limit, so the
208  // residual alone determines whether we found a valid root; testing
209  // niter < max_iter here would spuriously fail a solve that
210  // converged on exactly the last allowed iteration.
211  bool valid_solution = (std::abs(F) <= tol);
212  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(valid_solution, "SatAdj single phase saturation adjustment failed to converge.");
213 
214  return tabs;
215  }
216 
217  // Round-off tolerance for the debug-only moisture consistency and
218  // conservation checks, scaled to the working precision.
219  //
220  // NOTE: a hard-coded absolute tolerance (this used to be 1.e-14) is well
221  // below single-precision round-off -- for mixing ratios of order 1.e-2
222  // one ULP is roughly 1.e-9 in float -- so such a check fires
223  // spuriously in an AMREX_USE_FLOAT + AMREX_DEBUG build while still
224  // being far looser than round-off in double. Scaling by machine
225  // epsilon and the magnitude of the quantities compared keeps the
226  // check meaningful in both precisions.
227  AMREX_GPU_HOST_DEVICE
228  AMREX_FORCE_INLINE
229  static amrex::Real
231  {
233  * amrex::max(amrex::Real(1.0), std::abs(qtot));
234  }
235 
236  AMREX_GPU_HOST_DEVICE
237  AMREX_FORCE_INLINE
238  static void
239  // Cell-local fixed-pressure saturation adjustment.
240  // Inputs/outputs are specific quantities for one cell. The density is not
241  // used or modified. The update conserves qv + qc and, at the pressure
242  // passed in, conserves T + (L/cp) * qv. The final state should satisfy
243  // qc >= 0 and either qc == 0 or qv == qsat(T,p).
244  AdjustSatAdjCell (const amrex::Real fac_cond,
245  const amrex::Real rdOcp,
246  amrex::Real& tabs,
247  const amrex::Real pres_mbar,
249  amrex::Real& qv,
250  amrex::Real& qc)
251  {
252  amrex::Real qsat;
253  erf_qsatw(tabs, pres_mbar, qsat);
254 
255  if ((qv + qc) > qsat) {
256  // Total water exceeds saturation at the initial temperature, so the final state
257  // is cloudy and saturated. Solve directly for the final saturated temperature.
258  // This is algebraically equivalent to evaporating existing cloud water first
259  // and then recondensing to the fixed-pressure equilibrium state.
260 #if defined(AMREX_DEBUG)
261  const amrex::Real qvprev = qv;
262  const amrex::Real qcprev = qc;
263 #endif
264 
265  if (qc < 0) {
266  // Repair negative cloud water by transferring the deficit back to vapor
267  // before solving the saturated equilibrium and clamping qc to zero.
268  qv += qc;
269  qc = zero;
270  }
271 
272  tabs = NewtonSolveSatTemperature(fac_cond, tabs, pres_mbar, qv, qsat);
273 
274  const amrex::Real delta_qv = qv - qsat;
275  qv = qsat;
276  qc += delta_qv;
277 
278 #if defined(AMREX_DEBUG)
279  amrex::Real qsatnew;
280  erf_qsatw(tabs, pres_mbar, qsatnew);
281  const amrex::Real qtol = SatAdjMoistureTol(qvprev + qcprev);
282  AMREX_ASSERT(std::abs(qv-qsatnew) <= qtol);
283  AMREX_ASSERT(std::abs(qv+qc-qvprev-qcprev) <= qtol);
284 #endif
285 
286  theta = getThgivenTandP(tabs, amrex::Real(100.0)*pres_mbar, rdOcp);
287  } else {
288  // Total water does not exceed initial saturation. Evaporate all cloud water,
289  // cooling the cell by latent heat absorption. The cooled cell can become
290  // supersaturated, so recondense if needed.
291  const amrex::Real delta_qc = qc;
292 
293  qv += qc;
294  qc = zero;
295 
296  tabs -= fac_cond * delta_qc;
297  theta = getThgivenTandP(tabs, amrex::Real(100.0)*pres_mbar, rdOcp);
298 
299  erf_qsatw(tabs, pres_mbar, qsat);
300  if (qv > qsat) {
301 #if defined(AMREX_DEBUG)
302  const amrex::Real qvprev = qv;
303  const amrex::Real qcprev = qc;
304  const amrex::Real Tprev = tabs;
305 #endif
306 
307  tabs = NewtonSolveSatTemperature(fac_cond, tabs, pres_mbar, qv, qsat);
308 
309  const amrex::Real delta_qv = qv - qsat;
310  qv = qsat;
311  qc += delta_qv;
312 
313 #if defined(AMREX_DEBUG)
314  amrex::Real qsatnew;
315  erf_qsatw(tabs, pres_mbar, qsatnew);
316  const amrex::Real qtol = SatAdjMoistureTol(qvprev + qcprev);
317  AMREX_ASSERT(qv < qvprev);
318  AMREX_ASSERT(qc > qcprev);
319  AMREX_ASSERT(tabs > Tprev);
320  AMREX_ASSERT(std::abs(qv-qsatnew) <= qtol);
321  AMREX_ASSERT(std::abs(qv+qc-qvprev-qcprev) <= qtol);
322 #endif
323 
324  theta = getThgivenTandP(tabs, amrex::Real(100.0)*pres_mbar, rdOcp);
325  }
326  }
327  }
328 
329 private:
330  // Number of qmoist variables (no precipitating comps to accumulate)
331  int m_qmoist_size = 0;
332 
333  // Number of qstate variables
335 
336  // Number of qstate variables that are number concentrations
338 
339  // geometry
340  amrex::Geometry m_geom;
341 
342  // timestep
344 
345  // constants
348  bool m_do_cond;
349 
350  // independent variables
351  amrex::Array<FabPtr, MicVar_SatAdj::NumVars> mic_fab_vars;
352 };
353 #endif
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real getThgivenTandP(const amrex::Real T, const amrex::Real P, const amrex::Real rdOcp)
Definition: ERF_EOS.H:18
const Real rdOcp
Definition: ERF_InitCustomPert_ABL.H:72
AMREX_ALWAYS_ASSERT(bx.length()[2]==khi+1)
AMREX_ALWAYS_ASSERT_WITH_MESSAGE(m_cloud_chamber_config.active, "Cloud Chamber: initializer reached without a parsed configuration")
Physical constants and tuning parameters used only by the moisture and cloud-physics code.
constexpr amrex::Real lcond
Definition: ERF_MicrophysicsConstants.H:109
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void erf_dtqsatw(amrex::Real t, amrex::Real p, amrex::Real &dtqsatw)
Definition: ERF_MicrophysicsUtils.H:280
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void erf_qsatw(amrex::Real t, amrex::Real p, amrex::Real &qsatw)
Definition: ERF_MicrophysicsUtils.H:264
Dimensionless numeric literals and pure mathematical constants.
constexpr amrex::Real one
Definition: ERF_NumericalConstants.H:30
constexpr amrex::Real zero
Definition: ERF_NumericalConstants.H:29
amrex::Real Real
Definition: ERF_ShocInterface.H:19
Definition: ERF_NullMoist.H:10
void set_anelastic_reference_pressure_mode(const SolverChoice &sc)
Definition: ERF_NullMoist.H:155
Definition: ERF_SatAdj.H:47
void Update_Micro_Vars(amrex::MultiFab &cons_in, const amrex::MultiFab *base_state) override
int n_qstate_moist_size
Definition: ERF_SatAdj.H:334
AMREX_GPU_HOST_DEVICE static AMREX_FORCE_INLINE void AdjustSatAdjCell(const amrex::Real fac_cond, const amrex::Real rdOcp, amrex::Real &tabs, const amrex::Real pres_mbar, amrex::Real &theta, amrex::Real &qv, amrex::Real &qc)
Definition: ERF_SatAdj.H:244
amrex::Real m_fac_cond
Definition: ERF_SatAdj.H:346
int Qmoist_Size() override
Definition: ERF_SatAdj.H:127
void Update_Micro_Vars(amrex::MultiFab &cons_in) override
Definition: ERF_SatAdj.H:94
amrex::Geometry m_geom
Definition: ERF_SatAdj.H:340
void Qmoist_Restart_Vars(const SolverChoice &, std::vector< int > &a_idx, std::vector< std::string > &a_names) const override
Definition: ERF_SatAdj.H:136
amrex::Array< FabPtr, MicVar_SatAdj::NumVars > mic_fab_vars
Definition: ERF_SatAdj.H:351
amrex::Real m_rdOcp
Definition: ERF_SatAdj.H:347
SatAdj()
Definition: ERF_SatAdj.H:53
virtual ~SatAdj()=default
void Update_State_Vars(amrex::MultiFab &cons_in, const amrex::MultiFab &) override
Definition: ERF_SatAdj.H:102
amrex::MultiFab * Qmoist_Ptr(const int &varIdx) override
Definition: ERF_SatAdj.H:120
AMREX_GPU_HOST_DEVICE static AMREX_FORCE_INLINE amrex::Real SatAdjMoistureTol(const amrex::Real qtot)
Definition: ERF_SatAdj.H:230
int n_qstate_moist_numconc_size
Definition: ERF_SatAdj.H:337
int Qstate_Moist_NumConc_Size() override
Definition: ERF_SatAdj.H:133
int Qstate_Moist_Size() override
Definition: ERF_SatAdj.H:130
std::shared_ptr< amrex::MultiFab > FabPtr
Definition: ERF_SatAdj.H:49
void Copy_Micro_to_State(amrex::MultiFab &cons_in) override
Definition: ERF_UpdateSatAdj.cpp:13
bool m_do_cond
Definition: ERF_SatAdj.H:348
void Init(const amrex::MultiFab &cons_in, const amrex::BoxArray &, const amrex::Geometry &geom, const amrex::Real &dt_advance, std::unique_ptr< amrex::MultiFab > &, std::unique_ptr< amrex::MultiFab > &) override
Definition: ERF_InitSatAdj.cpp:17
void Advance(const amrex::Real &dt_advance, const SolverChoice &solverChoice) override
Definition: ERF_SatAdj.H:111
int m_qmoist_size
Definition: ERF_SatAdj.H:331
void AdvanceSatAdj(const SolverChoice &)
Definition: ERF_SatAdj.cpp:11
amrex::Real dt
Definition: ERF_SatAdj.H:343
void Define(SolverChoice &sc) override
Definition: ERF_SatAdj.H:63
AMREX_GPU_HOST_DEVICE static AMREX_FORCE_INLINE amrex::Real NewtonSolveSatTemperature(const amrex::Real fac_cond, const amrex::Real tabs_old, const amrex::Real pres_mbar, const amrex::Real qv_old, amrex::Real &qsat)
Definition: ERF_SatAdj.H:159
void Copy_State_to_Micro(const amrex::MultiFab &cons_in) override
Definition: ERF_InitSatAdj.cpp:44
Definition: ERF_SatAdj.H:33
@ theta
Definition: ERF_SatAdj.H:37
@ rho
Definition: ERF_SatAdj.H:36
@ pres
Definition: ERF_SatAdj.H:39
@ NumVars
Definition: ERF_SatAdj.H:43
@ qv
Definition: ERF_SatAdj.H:41
@ tabs
Definition: ERF_SatAdj.H:38
@ qc
Definition: ERF_SatAdj.H:42
real(c_double), parameter epsilon
Definition: ERF_module_model_constants.F90:12
Definition: ERF_DataStruct.H:662
amrex::Real rdOcp
Ratio of dry-air gas constant to c_p.
Definition: ERF_DataStruct.H:2062
bool uses_shoc_family() const noexcept
Query whether any SHOC-family PBL scheme is active.
Definition: ERF_DataStruct.H:2177
amrex::Real c_p
Specific heat at constant pressure for dry air [J/(kg-K)].
Definition: ERF_DataStruct.H:2061