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