FE-Project
Loading...
Searching...
No Matches
scale_atm_phy_cp_dgm_mconv_adjustment.F90
Go to the documentation of this file.
1!> module FElib / Atmosphere / Physics cumulus parameterization
2!!
3!! @par Description
4!! A module providing a moist convective adjustment scheme.
5!!
6!! Saturated convective instability is diagnosed from the vertical gradient of saturated moist static energy in nearly saturated layers.
7!! The diagnosed layer is adjusted toward a moist-neutral temperature profile subject to column-integrated moist-static-energy and available-water constraints.
8!! Condensed water is immediately removed as precipitation.
9!!
10!! @author Yuta Kawai, Team SCALE
11!!
12!! @par Reference
13!! Manabe, S., J. Smagorinsky, and R. F. Strickler (1965):
14!! Simulated Climatology of a General Circulation Model with a
15!! Hydrologic Cycle. Monthly Weather Review, 93, 769-798.
16!!
17!!
18!-------------------------------------------------------------------------------
19#include "scaleFElib.h"
21 !-----------------------------------------------------------------------------
22 !
23 !++ Used modules
24 !
25 use scale_precision
26 use scale_io
27 use scale_prc
28 use scale_prof
29 use scale_const, only: &
30 grav => const_grav, &
31 cpdry => const_cpdry, &
32 rdry => const_rdry, &
33 rvap => const_rvap, &
34 pres0 => const_pre00, &
35 eps => const_eps
36 use scale_atmos_hydrometeor, only: &
37 cp_vapor, &
38 lhv
39 use scale_atmos_saturation, only: &
40 atmos_saturation_psat_liq, &
41 atmos_saturation_pres2qsat_liq
42
43 use scale_element_base, only: &
47
48 !-----------------------------------------------------------------------------
49 implicit none
50 private
51 !-----------------------------------------------------------------------------
52 !
53 !++ Public type & procedure
54 !
58
59 !-----------------------------------------------------------------------------
60 !++ Public parameters & variables
61 !
62 !-----------------------------------------------------------------------------
63 !
64 !++ Private procedure
65 !
66 !-----------------------------------------------------------------------------
67
68 !* MCA paramters
69
70 ! Iteration limits
71 integer, parameter :: MCA_MAX_ADJUST_ITER = 200
72 integer, parameter :: MCA_MAX_ENERGY_ITER = 50
73 integer, parameter :: MCA_MAX_WATER_ITER = 60
74
75 ! Column-energy root solve
76 real(RP), parameter :: MCA_COLUMN_ENERGY_RTOL = 5.0e-7_rp
77 real(RP), parameter :: MCA_COLUMN_ENERGY_ATOL = 1.0_rp
78 real(RP), parameter :: MCA_BOUNDARY_ENERGY_RTOL = 1.0e-6_rp
79
80 ! Local saturated-MSE solve for moist-adiabat integration
81 real(RP), parameter :: MCA_LOCAL_HMSE_RTOL = 1.0e-9_rp
82 real(RP), parameter :: MCA_LOCAL_HMSE_ATOL = 1.0e-4_rp
83 real(RP), parameter :: MCA_TEMP_TOL = 1.0e-5_rp
84
85 ! Water-feasibility constraint
86 real(RP), parameter :: MCA_WATER_RTOL = 1.0e-9_rp
87 real(RP), parameter :: MCA_WATER_ATOL = 1.0e-10_rp
88
89 ! Saturation and instability thresholds
90 real(RP), parameter :: MCA_RH_FORCED_SATURATION = 0.999_rp
91 real(RP), parameter :: MCA_RH_TRIGGER = 0.999_rp
92
93 ! Thresholds for suppressing numerical-noise detection
94 real(RP), parameter :: MCA_HMSE_GRAD_TOL = 1.0e-3_rp
95 real(RP), parameter :: MCA_HMSE_DIFF_TOL = 1.0_rp
96 real(RP), parameter :: MCA_MIN_UNSTABLE_DEPTH = 0.0_rp
97 real(RP), parameter :: MCA_Z_TOL = 100.0_rp * epsilon(1.0_rp)
98
99 ! Moist-neutral-profile solver status
100 integer, parameter :: MCA_PROFILE_SUCCESS = 0
101 integer, parameter :: MCA_PROFILE_NO_ENERGY_ROOT = 1
102 integer, parameter :: MCA_PROFILE_ADIABAT_FAILURE = 2
103 integer, parameter :: MCA_PROFILE_ENERGY_MAXITER = 3
104 integer, parameter :: MCA_PROFILE_NO_ADJUSTED_NODE = 4
105 integer, parameter :: MCA_PROFILE_NO_WATER_FEASIBLE_STATE = 5
106
107contains
108 !> Setup a module for moist convective adjustment scheme
110 implicit none
111 !----------------------------------------------------
112 return
114
115 !> Calculate tendencies with moist convective adjustment scheme
116!OCL SERIAL
118 DENS_t, RHOT_t, RHOQV_t, SFLX_RAIN, SFLX_ENGI, & ! (out)
119 ddens, drhot, qv, pt, pres, dens_hyd, rtot, cptot, & ! (in)
120 dtsec, lmesh, elem, elem1d ) ! (in)
121
122 use scale_tracer, only: &
123 tracer_cv
124 use scale_atmos_hydrometeor, only: &
125 cv_water, i_qv
126 implicit none
127 class(localmesh3d), intent(in) :: lmesh
128 class(elementbase3d), intent(in) :: elem
129 class(elementbase1d), intent(in) :: elem1d
130 real(rp), intent(out) :: dens_t(elem%np,lmesh%nea)
131 real(rp), intent(out) :: rhot_t(elem%np,lmesh%nea)
132 real(rp), intent(out) :: rhoqv_t(elem%np,lmesh%nea)
133 real(rp), intent(out) :: sflx_rain(elem%nnode_h1d**2,lmesh%ne2da)
134 real(rp), intent(out) :: sflx_engi(elem%nnode_h1d**2,lmesh%ne2da)
135 real(rp), intent(in) :: ddens(elem%np,lmesh%nea)
136 real(rp), intent(in) :: drhot(elem%np,lmesh%nea)
137 real(rp), intent(in) :: qv(elem%np,lmesh%nea)
138 real(rp), intent(in) :: pt(elem%np,lmesh%nea)
139 real(rp), intent(in) :: pres(elem%np,lmesh%nea)
140 real(rp), intent(in) :: dens_hyd(elem%np,lmesh%nea)
141 real(rp), intent(in) :: rtot(elem%np,lmesh%nea)
142 real(rp), intent(in) :: cptot(elem%np,lmesh%nea)
143 real(rp), intent(in) :: dtsec
144
145 integer :: ke, ke_xy, ke_z
146 integer :: ph, pz, p
147 real(rp) :: dens_z(elem%nnode_v,lmesh%nez)
148 real(rp) :: pres_z(elem%nnode_v,lmesh%nez)
149 real(rp) :: zlev_z(elem%nnode_v,lmesh%nez)
150 real(rp) :: temp_z(elem%nnode_v,lmesh%nez)
151 real(rp) :: pott_z(elem%nnode_v,lmesh%nez) ! work array
152 real(rp) :: qvap_z(elem%nnode_v,lmesh%nez) ! work array
153 real(rp) :: qsat
154 real(rp) :: rh_z
155 real(rp) :: rtot_, cptot_, qdry
156
157 real(rp) :: elem_width_z
158 real(rp) :: int_weight(elem%nnode_v,lmesh%nez)
159
160 real(rp) :: pott_ini(elem%nnode_v,lmesh%nez)
161 real(rp) :: qvap_ini(elem%nnode_v,lmesh%nez)
162 real(rp) :: dens_ini(elem%nnode_v,lmesh%nez)
163 real(rp) :: rhoqvap_ini(elem%nnode_v,lmesh%nez)
164
165 integer :: iter_expand
166 integer :: mca_max_mask_expand
167 integer :: lbase, ltop, lbase_try, ltop_try
168 real(rp) :: zbase_try, ztop_try
169 logical :: mask_expanded
170
171 logical :: is_unstable
172 logical :: unstable_core_mask(elem%nnode_v,lmesh%nez)
173 logical :: forced_saturation_mask(elem%nnode_v,lmesh%nez)
174 logical :: adjustment_mask(elem%nnode_v,lmesh%nez)
175
176 logical :: current_saturation_mask(elem%nnode_v,lmesh%nez)
177 logical :: persistent_saturation_mask(elem%nnode_v,lmesh%nez)
178
179 logical :: physical_active_mask(elem%nnode_v,lmesh%nez)
180 integer :: iter_adj
181 real(rp) :: temp_adj(elem%nnode_v,lmesh%nez)
182 real(rp) :: qvap_adj(elem%nnode_v,lmesh%nez)
183 real(rp) :: rhoqvap_adj(elem%nnode_v,lmesh%nez)
184 real(rp) :: rhoprecip_adj(elem%nnode_v,lmesh%nez) ! Water-vapor mass density diagnosed to be removed as precipitation during the current adjustment iteration [kg m-3]
185 real(rp) :: rhoprecip_accum(elem%nnode_v,lmesh%nez) ! Accumulated vapor mass density removed as precipitation [kg m-3]
186 real(rp) :: precip_engi_accum
187
188 real(rp), allocatable :: zlev_diag(:)
189 integer :: nlev_diag
190
191 logical :: profile_converged
192 integer :: profile_status
193 logical :: do_adjustment
194 logical :: adjustment_converged
195
196 integer :: hslice_b(elem%nnode_h1d**2), hslice_t(elem%nnode_h1d**2)
197
198 logical :: debug_flag
199
200 integer :: lactive_base
201 integer :: lactive_top
202 logical :: active_region_initialized
203 !----------------------------------------------------
204
205 nlev_diag = lmesh%NeZ * (elem%Nnode_v-1) + 1
206 allocate( zlev_diag(nlev_diag) )
207
208 hslice_b(:) = elem%Hslice(:,1)
209 hslice_t(:) = elem%Hslice(:,elem%Nnode_v)
210
211 mca_max_mask_expand = nlev_diag
212
213 !$omp parallel do collapse(2) private(ke,p, &
214 !$omp dens_z, pres_z, temp_z, zlev_z, pott_z, qvap_z, qsat, rh_z, rtot_,cptot_, &
215 !$omp dens_ini, pott_ini, qvap_ini, rhoqvap_ini, &
216 !$omp zlev_diag, &
217 !$omp lbase, ltop, lbase_try, ltop_try, zbase_try, ztop_try, iter_expand, mask_expanded, &
218 !$omp is_unstable, unstable_core_mask, forced_saturation_mask, adjustment_mask, &
219 !$omp current_saturation_mask, persistent_saturation_mask, physical_active_mask, &
220 !$omp temp_adj, qdry, qvap_adj, rhoqvap_adj, rhoprecip_adj, rhoprecip_accum, &
221 !$omp profile_converged, profile_status, do_adjustment, adjustment_converged, &
222 !$omp int_weight,elem_width_z, debug_flag, lactive_base, lactive_top, active_region_initialized )
223 do ke_xy=1, lmesh%Ne2D
224 do ph=1, elem%Nnode_h1D**2
225
226 ! if ( lmesh%PRC_myrank == 1 .and. ph == 32 .and. ke_xy == 13 ) then
227 ! debug_flag = .true.
228 ! else
229 debug_flag = .false.
230 ! end if
231
232 !- Extract vertical 1D DG column
233
234 do ke_z=1, lmesh%NeZ
235 ke = ke_xy + (ke_z-1)*lmesh%Ne2D
236
237 elem_width_z = lmesh%zlev(hslice_t(ph),ke) - lmesh%zlev(hslice_b(ph),ke)
238 do pz=1, elem%Nnode_v
239 p = ph + (pz-1)*elem%Nnode_h1D**2
240 dens_z(pz,ke_z) = ddens(p,ke) + dens_hyd(p,ke)
241 pres_z(pz,ke_z) = pres(p,ke)
242 zlev_z(pz,ke_z) = lmesh%zlev(p,ke)
243 int_weight(pz,ke_z) = elem1d%IntWeight_lgl(pz) * 0.5_rp * elem_width_z
244
245 temp_z(pz,ke_z) = pres(p,ke)/ ( dens_z(pz,ke_z) * rtot(p,ke) )
246 qvap_z(pz,ke_z) = qv(p,ke)
247 pott_z(pz,ke_z) = pt(p,ke)
248 end do
249 end do
250
251 !- Set initial state
252
253 do ke_z=1, lmesh%NeZ
254 dens_ini(:,ke_z) = dens_z(:,ke_z)
255 pott_ini(:,ke_z) = pott_z(:,ke_z)
256 qvap_ini(:,ke_z) = qvap_z(:,ke_z)
257 rhoqvap_ini(:,ke_z) = dens_ini(:,ke_z) * qvap_ini(:,ke_z)
258
259 temp_adj(:,ke_z) = temp_z(:,ke_z)
260 rhoqvap_adj(:,ke_z) = rhoqvap_ini(:,ke_z)
261 rhoprecip_accum(:,ke_z) = 0.0_rp
262 end do
263
264 !* Iteratively diagnose and remove remaining saturated convective instability. ***************************************
265 ! A single profile adjustment may generate a new unstable layer adjacent to or outside the previously adjusted region.
266
267 adjustment_converged = .false.
268 do_adjustment = .false.
269
270 active_region_initialized = .false.
271 lactive_base = 0
272 lactive_top = 0
273
274 forced_saturation_mask(:,:) = .false.
275 current_saturation_mask(:,:) = .false.
276 persistent_saturation_mask(:,:) = .false.
277 physical_active_mask(:,:) = .false.
278
279 do iter_adj=1, mca_max_adjust_iter
280
281 qvap_adj(:,:) = rhoqvap_adj(:,:) / dens_z(:,:)
282
283 ! Reconstruct the saturation constraint from the current state.
284 current_saturation_mask(:,:) = .false.
285 forced_saturation_mask(:,:) = .false.
286
287 !- Diagnose unstable layers
288
289 call diagnose_convective_layer( lbase, ltop, is_unstable, zlev_diag, & ! (out)
290 temp_adj, qvap_adj, pres_z, zlev_z, lmesh, elem, nlev_diag, debug_flag ) ! (in)
291
292 if ( debug_flag ) then
293 write(*,*) "ke_xy=", ke_xy, "ph=", ph, "iter_adj=", iter_adj
294 write(*,*) " lbase, ltop, is_unstable = ", lbase, ltop, is_unstable
295 end if
296
297 if ( .not. is_unstable ) then
298 adjustment_converged = .true.
299 exit
300 end if
301 do_adjustment = .true.
302
303 ! Maintain the smallest diagnostic-level interval containing
304 ! all physically diagnosed unstable regions belonging to the
305 ! current connected convective-adjustment event.
306 if ( .not. active_region_initialized ) then
307 lactive_base = lbase
308 lactive_top = ltop
309 active_region_initialized = .true.
310 else if ( lbase <= lactive_top + 1 .and. &
311 ltop >= lactive_base - 1 ) then
312 ! Overlapping or directly touching region:
313 ! continue the same connected adjustment.
314 lactive_base = min(lactive_base, lbase)
315 lactive_top = max(lactive_top, ltop)
316 else
317 ! Disconnected instability:
318 ! start a new independent adjustment region.
319 lactive_base = lbase
320 lactive_top = ltop
321 persistent_saturation_mask(:,:) = .false.
322 end if
323
324 if ( debug_flag ) then
325 write(*,*) " persistent active region=", lactive_base, lactive_top
326 end if
327
328 ! Construct the physically connected active region.
329 ! This does not include numerical expansion used only for water/energy feasibility.
330 call make_dg_vertical_range_mask( &
331 physical_active_mask, & ! (out)
332 zlev_z, zlev_diag(lactive_base), zlev_diag(lactive_top), & ! (in)
333 elem%Nnode_v, lmesh%NeZ ) ! (in)
334
335 ! Mark nearly saturated DG nodes inside the diagnosed unstable core.
336 ! These nodes will be constrained to saturation in each trial moist-neutral profile.
337 call make_dg_vertical_range_mask( &
338 unstable_core_mask, & ! (out)
339 zlev_z, zlev_diag(lbase), zlev_diag(ltop), & ! (in)
340 elem%Nnode_v, lmesh%NeZ ) ! (in)
341
342 do ke_z = 1, lmesh%NeZ
343 do pz = 1, elem%Nnode_v
344 if ( unstable_core_mask(pz,ke_z) ) then
345 call atmos_saturation_pres2qsat_liq( temp_adj(pz,ke_z), pres_z(pz,ke_z), &
346 qsat ) ! (out)
347
348 rh_z = qvap_adj(pz,ke_z) / max(qsat, eps)
349 current_saturation_mask(pz,ke_z) = rh_z >= mca_rh_forced_saturation
350 end if
351 end do
352 end do
353
354 ! Keep saturation constraints inherited from previous adjustment iterations within this connected convective event.
355
356 forced_saturation_mask(:,:) = persistent_saturation_mask(:,:) .or. current_saturation_mask(:,:)
357
358 !- Construct a moist-neutral profile.
359 ! If no water-feasible and energy-conserving solution exists, expand the adjustment layer and retry.
360
361 lbase_try = lactive_base
362 ltop_try = lactive_top
363
364 profile_converged = .false.
365 profile_status = mca_profile_energy_maxiter
366
367 do iter_expand=0, mca_max_mask_expand
368 zbase_try = zlev_diag(lbase_try)
369 ztop_try = zlev_diag(ltop_try)
370
371 !- Construct a mask for the adjustment layer
372 call make_dg_vertical_range_mask( adjustment_mask, & ! (out)
373 zlev_z, zbase_try, ztop_try, elem%Nnode_v, lmesh%NeZ ) ! (in)
374
375 if ( debug_flag ) then
376 write(*,*) "* Adjustment-profile attempt: iter_expand=", iter_expand
377 write(*,*) " lactive_base, lactive_top=", lactive_base, lactive_top
378 write(*,*) " lbase_try, ltop_try=", lbase_try, ltop_try
379 write(*,*) " zbase_try, ztop_try=", zbase_try, ztop_try
380 write(*,*) " saturation, mixing nodes=", count(forced_saturation_mask), count(adjustment_mask)
381 end if
382
383 !- Solve for a water-feasible and energy-conserving trial moist-neutral profile
384
385 call solve_moist_neutral_adjustment( &
386 temp_adj, qvap_adj, rhoqvap_adj, rhoprecip_adj, profile_converged, profile_status, & ! (out)
387 qvap_z, pres_z, zlev_z, dens_z, temp_z, & ! (in)
388 int_weight, adjustment_mask, forced_saturation_mask, elem%Nnode_v, lmesh%NeZ, debug_flag ) ! (in)
389
390 if ( profile_converged ) then
391 ! Update the persistent saturation history using the post-adjustment state in the physically connected region.
392 do ke_z = 1, lmesh%NeZ
393 do pz = 1, elem%Nnode_v
394 if ( physical_active_mask(pz,ke_z) ) then
395
396 call atmos_saturation_pres2qsat_liq( temp_adj(pz,ke_z), pres_z(pz,ke_z), &
397 qsat )
398
399 rh_z = qvap_adj(pz,ke_z) / qsat
400 if ( rh_z >= mca_rh_forced_saturation ) then
401 persistent_saturation_mask(pz,ke_z) = .true.
402 end if
403 end if
404 end do
405 end do
406
407 if ( debug_flag ) then
408 write(*,*) "Moist-neutral profile converged: iter_expand=", iter_expand
409 write(*,*) "final solve region: lbase, ltop=", lbase_try, ltop_try
410 write(*,*) "physical active region=", lactive_base, lactive_top
411 write(*,*) "persistent saturated nodes=", count(persistent_saturation_mask)
412 end if
413 exit
414 end if
415
416 select case ( profile_status )
417 case ( mca_profile_no_energy_root )
418 call expand_adjustment_layer( lbase_try, ltop_try, mask_expanded, nlev_diag )
419 if ( .not. mask_expanded ) then
420 exit
421 end if
422 case default
423 exit
424 end select
425
426 end do ! end loop for iter_expand
427
428 if ( .not. profile_converged ) then
429 log_info("atm_phy_cp_dgm_mconv_adjustment_calc_tendency",*) "iter_adj=", iter_adj, "ph=", ph, "ke_xy=", ke_xy, "iter_expand=", iter_expand
430 select case ( profile_status )
431 case ( mca_profile_no_energy_root )
432 log_info("atm_phy_cp_dgm_mconv_adjustment_calc_tendency",*) "No energy-conserving root exists in the water-feasible temperature interval, even after adjustment-layer expansion."
433 log_info("atm_phy_cp_dgm_mconv_adjustment_calc_tendency",*) "original lbase,ltop=", lbase, ltop, "final lbase,ltop=", lbase_try, ltop_try
434 do_adjustment = .false.
435 exit
436 ! call PRC_abort
437 case ( mca_profile_no_water_feasible_state )
438 log_info("atm_phy_cp_dgm_mconv_adjustment_calc_tendency",*) "No water-feasible trial moist-neutral profile exists."
439 call prc_abort
440 case ( mca_profile_adiabat_failure )
441 log_info("atm_phy_cp_dgm_mconv_adjustment_calc_tendency",*) "Moist-adiabat profile integration failed."
442 call prc_abort
443 case ( mca_profile_energy_maxiter )
444 log_info("atm_phy_cp_dgm_mconv_adjustment_calc_tendency",*) "Energy root was bracketed, but bisection did not converge."
445 call prc_abort
446 case ( mca_profile_no_adjusted_node )
447 log_info("atm_phy_cp_dgm_mconv_adjustment_calc_tendency",*) "No adjusted DG node was found."
448 call prc_abort
449 case default
450 log_info("atm_phy_cp_dgm_mconv_adjustment_calc_tendency",*) "Unknown moist-neutral-profile construction error."
451 call prc_abort
452 end select
453 end if
454
455 ! Accumulate the precipitation density from the current iteration
456 rhoprecip_accum(:,:) = rhoprecip_accum(:,:) + rhoprecip_adj(:,:)
457
458 ! Update the state for the next iteration
459 temp_z(:,:) = temp_adj(:,:)
460 qvap_z(:,:) = qvap_adj(:,:)
461
462 do ke_z = 1, lmesh%NeZ
463 do pz = 1, elem%Nnode_v
464 qdry = 1.0_rp - qvap_z(pz,ke_z)
465 rtot_ = rdry * qdry &
466 + rvap * qvap_z(pz,ke_z)
467 pres_z(pz,ke_z) = dens_z(pz,ke_z) * rtot_ * temp_z(pz,ke_z)
468 enddo
469 enddo
470 end do ! End loop for iteration
471
472 if ( do_adjustment .and. ( .not. adjustment_converged ) ) then
473 log_info("atm_phy_cp_dgm_mconv_adjustment_calc_tendency",*) "Moist convective adjustment did not converge: ph=", ph, "ke_xy=", ke_xy
474 call prc_abort
475 end if
476
477
478 !- Convert the adjusted state to DG tendencies
479
480 sflx_rain(ph,ke_xy) = 0.0_rp
481 sflx_engi(ph,ke_xy) = 0.0_rp
482
483 if ( do_adjustment ) then
484
485 do ke_z=1, lmesh%NeZ
486 ke = ke_xy + (ke_z-1)*lmesh%Ne2D
487
488 dens_z(:,ke_z) = dens_ini(:,ke_z) - rhoprecip_accum(:,ke_z)
489 qvap_z(:,ke_z) = rhoqvap_adj(:,ke_z) / dens_z(:,ke_z)
490
491 do pz=1, elem%Nnode_v
492 qdry = 1.0_rp - qvap_z(pz,ke_z)
493 rtot_ = rdry * qdry + rvap * qvap_z(pz,ke_z)
494 cptot_ = cpdry * qdry + cp_vapor * qvap_z(pz,ke_z)
495
496 pres_z(pz,ke_z) = dens_z(pz,ke_z) * rtot_ * temp_z(pz,ke_z)
497 pott_z(pz,ke_z) = temp_adj(pz,ke_z) * ( pres0 / pres_z(pz,ke_z) )**( rtot_ / cptot_ )
498
499 sflx_rain(ph,ke_xy) = sflx_rain(ph,ke_xy) &
500 - rhoprecip_accum(pz,ke_z) * int_weight(pz,ke_z) / dtsec
501 sflx_engi(ph,ke_xy) = sflx_engi(ph,ke_xy) &
502 - temp_adj(pz,ke_z) * rhoprecip_accum(pz,ke_z) * int_weight(pz,ke_z) * cptot_ / dtsec
503 end do
504
505 do pz=1, elem%Nnode_v
506 p = ph + (pz-1)*elem%Nnode_h1D**2
507 dens_t(p,ke) = ( dens_z(pz,ke_z) - dens_ini(pz,ke_z) ) / dtsec
508 rhot_t(p,ke) = ( dens_z(pz,ke_z) * pott_z(pz,ke_z) - dens_ini(pz,ke_z) * pott_ini(pz,ke_z) ) / dtsec
509 rhoqv_t(p,ke) = ( dens_z(pz,ke_z) * qvap_z(pz,ke_z) - rhoqvap_ini(pz,ke_z) ) / dtsec
510 end do
511 end do
512
513 if ( debug_flag ) then
514 write(*,*) " MCA summary:"
515 write(*,*) " del_DENS: ", dens_z(:,:) - dens_ini(:,:)
516 write(*,*) " del_RHOT: ", dens_z(:,:) * pott_z(:,:) - dens_ini(:,:) * pott_ini(:,:)
517 write(*,*) " del_RHOQV: ", dens_z(:,:) * qvap_z(:,:) - rhoqvap_ini(:,:)
518 write(*,*) "----------------------------------------------------------------"
519 end if
520 else
521 do ke_z=1, lmesh%NeZ
522 ke = ke_xy + (ke_z-1)*lmesh%Ne2D
523 do pz=1, elem%Nnode_v
524 p = ph + (pz-1)*elem%Nnode_h1D**2
525 dens_t(p,ke) = 0.0_rp
526 rhot_t(p,ke) = 0.0_rp
527 rhoqv_t(p,ke) = 0.0_rp
528 end do
529 end do
530 end if
531
532 end do ! end loop for ph
533 end do ! end loop for ke_xy
534
535 return
537
538 !> Finalize a module for moist convective adjustment scheme
540 implicit none
541 !----------------------------------------------------
542 return
544
545
546!- private subroutines --------------------------
547
548!OCL SERIAL
549 subroutine build_diagnostic_profile_from_dg( &
550 hmse_diag, rh_diag, zlev_diag, & ! (out)
551 hmse_z, rh_z, zlev_z, lmesh, elem, nlev ) ! (in)
552 implicit none
553 class(localmesh3d), intent(in) :: lmesh
554 class(elementbase3d), intent(in) :: elem
555 integer, intent(in) :: nlev
556 real(rp), intent(out) :: hmse_diag(nlev)
557 real(rp), intent(out) :: rh_diag(nlev)
558 real(rp), intent(out) :: zlev_diag(nlev)
559 real(rp), intent(in) :: hmse_z(elem%nnode_v,lmesh%nez)
560 real(rp), intent(in) :: rh_z(elem%nnode_v,lmesh%nez)
561 real(rp), intent(in) :: zlev_z(elem%nnode_v,lmesh%nez)
562
563 integer :: pz, ke_z
564 integer :: l
565 integer :: nnode_v
566 !----------------------------------------------------
567
568 nnode_v = elem%Nnode_v
569 l = 1
570
571 hmse_diag(l) = hmse_z(1,1)
572 rh_diag(l) = rh_z(1,1)
573 zlev_diag(l) = zlev_z(1,1)
574 do ke_z=1, lmesh%NeZ
575 do pz=2, nnode_v-1
576 l = l + 1
577 hmse_diag(l) = hmse_z(pz,ke_z)
578 rh_diag(l) = rh_z(pz,ke_z)
579 zlev_diag(l) = zlev_z(pz,ke_z)
580 end do
581 l = l + 1
582 if ( ke_z < lmesh%NeZ ) then
583 hmse_diag(l) = 0.5_rp * ( hmse_z(nnode_v,ke_z) + hmse_z(1,ke_z+1) )
584 rh_diag(l) = 0.5_rp * ( rh_z(nnode_v,ke_z) + rh_z(1,ke_z+1) )
585 zlev_diag(l) = 0.5_rp * ( zlev_z(nnode_v,ke_z) + zlev_z(1,ke_z+1) )
586 else
587 hmse_diag(l) = hmse_z(nnode_v,ke_z)
588 rh_diag(l) = rh_z(nnode_v,ke_z)
589 zlev_diag(l) = zlev_z(nnode_v,ke_z)
590 end if
591 end do
592 return
593 end subroutine build_diagnostic_profile_from_dg
594
595!OCL SERIAL
596 subroutine diagnose_convective_layer( lbase, ltop, is_unstable, zlev_diag, & ! (out)
597 temp_z, qvap_z, pres_z, zlev_z, lmesh, elem, nlev, debug_flag ) ! (in)
598 implicit none
599 class(localmesh3d), intent(in) :: lmesh
600 class(elementbase3d), intent(in) :: elem
601 integer, intent(in) :: nlev
602 integer, intent(out) :: lbase
603 integer, intent(out) :: ltop
604 logical, intent(out) :: is_unstable
605 real(rp), intent(out) :: zlev_diag(nlev)
606 real(rp), intent(in) :: temp_z(elem%nnode_v,lmesh%nez)
607 real(rp), intent(in) :: qvap_z(elem%nnode_v,lmesh%nez)
608 real(rp), intent(in) :: pres_z(elem%nnode_v,lmesh%nez)
609 real(rp), intent(in) :: zlev_z(elem%nnode_v,lmesh%nez)
610 logical, intent(in) :: debug_flag
611
612 integer :: pz, ke_z
613 integer :: l
614
615 real(rp) :: hmse_sat_z(elem%nnode_v,lmesh%nez)
616 real(rp) :: rh_z(elem%nnode_v,lmesh%nez)
617 real(rp) :: qsat, cptot
618
619 real(rp) :: rh(nlev)
620 real(rp) :: hmse_sat(nlev)
621
622 real(rp) :: dz
623 real(rp) :: dhmse_sat, dhmse_sat_dz
624 real(rp) :: rh_lyr
625 logical :: unstable_pair
626 logical :: is_inside
627
628 integer :: nnode_v
629 !------------------------------------
630
631 nnode_v = elem%Nnode_v
632
633 do ke_z=1, lmesh%NeZ
634 do pz=1, nnode_v
635
636 call atmos_saturation_pres2qsat_liq( temp_z(pz,ke_z), pres_z(pz,ke_z), &
637 qsat ) ! (out)
638
639 rh_z(pz,ke_z) = qvap_z(pz,ke_z) / max(qsat, eps)
640
641 cptot = cpdry * (1.0_rp - qsat) + cp_vapor * qsat
642 hmse_sat_z(pz,ke_z) = cptot * temp_z(pz,ke_z) + grav * zlev_z(pz,ke_z) + lhv * qsat
643 end do
644 end do
645
646 call build_diagnostic_profile_from_dg( hmse_sat, rh, zlev_diag, &
647 hmse_sat_z, rh_z, zlev_z, &
648 lmesh, elem, nlev )
649
650 !- Diagnose unstable layers based on the saturated MSE profile and relative humidity
651
652 is_unstable = .false.
653 is_inside = .false.
654 lbase = 0; ltop = 0
655
656 do l=1, nlev-1
657 dz = zlev_diag(l+1) - zlev_diag(l)
658 dhmse_sat = hmse_sat(l+1) - hmse_sat(l)
659 dhmse_sat_dz = dhmse_sat / dz
660 rh_lyr = 0.5_rp * ( rh(l) + rh(l+1) )
661 ! write(*,*) "l=", l, "zlev(l)=", zlev(l), "zlev(l+1)=", zlev(l+1), &
662 ! "rh_lyr=", rh_lyr, "dhmse_sat_dz=", dhmse_sat_dz
663 unstable_pair = &
664 ( min(rh(l), rh(l+1)) >= mca_rh_trigger ) &
665 .and. ( dhmse_sat < -mca_hmse_diff_tol ) &
666 .and. ( dhmse_sat_dz < -mca_hmse_grad_tol )
667
668
669 if ( unstable_pair ) then
670 if ( .not. is_inside ) then
671 lbase = l
672 is_inside = .true.
673 end if
674 ltop = l + 1
675 else if ( is_inside ) then
676 exit
677 end if
678 end do
679
680 if ( is_inside ) then
681 if ( zlev_diag(ltop) - zlev_diag(lbase) >= mca_min_unstable_depth ) is_unstable = .true.
682 end if
683
684 if ( debug_flag .and. is_unstable ) then
685 write(*,*) "Diagnose convective layer:"
686 write(*,*) "hmse_sat=", hmse_sat(lbase:ltop)
687 write(*,*) "rh=", rh(lbase:ltop)
688 write(*,*) "hmse_sat_dg=", hmse_sat_z(:,:)
689 write(*,*) "rh_dg=", rh_z(:,:)
690 end if
691
692 return
693 end subroutine diagnose_convective_layer
694
695!OCL SERIAL
696 subroutine make_dg_vertical_range_mask( adjustment_mask, & ! (out)
697 zlev, zbase, ztop, npz, nez ) ! (in)
698 implicit none
699 integer, intent(in) :: npz
700 integer, intent(in) :: nez
701 logical, intent(out) :: adjustment_mask(npz,nez)
702 real(rp), intent(in) :: zlev(npz,nez)
703 real(rp), intent(in) :: zbase
704 real(rp), intent(in) :: ztop
705
706 integer :: ke_z, pz
707 !----------------------------------------------------
708
709 adjustment_mask(:,:) = .false.
710 do ke_z = 1, nez
711 do pz = 1, npz
712 if ( zlev(pz,ke_z) >= zbase - mca_z_tol .and. &
713 zlev(pz,ke_z) <= ztop + mca_z_tol ) then
714 adjustment_mask(pz,ke_z) = .true.
715 end if
716 end do
717 end do
718 return
719 end subroutine make_dg_vertical_range_mask
720
721 !> Expand the adjustment layer by one diagnostic level above and below, if possible
722 !! Numerical fallback: enlarge the adjustment region by one diagnostic level on each available side. This is not a physically based entrainment closure.
723!OCL SERIAL
724 subroutine expand_adjustment_layer( &
725 lbase, ltop, expanded, & ! (inout,out)
726 nlev ) ! (in)
727 implicit none
728
729 integer, intent(inout) :: lbase
730 integer, intent(inout) :: ltop
731 logical, intent(out) :: expanded
732 integer, intent(in) :: nlev
733
734 logical :: can_expand_below
735 logical :: can_expand_above
736 !------------------------------------------------------------
737
738 can_expand_below = lbase > 1
739 can_expand_above = ltop < nlev
740
741 expanded = .false.
742
743 ! Expand by one diagnostic level on each side where possible.
744
745 if ( can_expand_below ) then
746 lbase = lbase - 1
747 expanded = .true.
748 end if
749
750 if ( can_expand_above ) then
751 ltop = ltop + 1
752 expanded = .true.
753 end if
754 return
755 end subroutine expand_adjustment_layer
756
757 !> Solve for a trial moist-neutral profile satisfying: 1. available-water constraint, 2. column-integrated moist-static-energy conservation.
758 !!
759 !! The pressure and density profiles are held fixed during the solve.
760 !! Nodes selected by forced_saturation_mask are set to saturation.
761 !! At other adjusted nodes, the original vapor mixing ratio is retained unless it exceeds saturation.
762 !!
763!OCL SERIAL
764 subroutine solve_moist_neutral_adjustment( &
765 temp_adj, qvap_adj, rhoqvap_adj, rhoprecip_adj, converged, profile_status, & ! (out)
766 qvap, pres, zlev, dens, temp, & ! (in)
767 int_weight, mix_mask, saturation_mask, npz, nez, debug_flag ) ! (in)
768
769 implicit none
770 integer, intent(in) :: npz
771 integer, intent(in) :: nez
772 real(rp), intent(out) :: temp_adj(npz,nez)
773 real(rp), intent(out) :: qvap_adj(npz,nez)
774 real(rp), intent(out) :: rhoqvap_adj(npz,nez)
775 real(rp), intent(out) :: rhoprecip_adj(npz,nez)
776 logical, intent(out) :: converged
777 integer, intent(out) :: profile_status
778 real(rp), intent(in) :: qvap(npz,nez)
779 real(rp), intent(in) :: pres(npz,nez)
780 real(rp), intent(in) :: zlev(npz,nez)
781 real(rp), intent(in) :: dens(npz,nez)
782 real(rp), intent(in) :: temp(npz,nez)
783 real(rp), intent(in) :: int_weight(npz,nez)
784 logical, intent(in) :: mix_mask(npz,nez)
785 logical, intent(in) :: saturation_mask(npz,nez)
786 logical, intent(in) :: debug_flag
787
788 ! Initial column-integrated quantities
789 real(rp) :: energy_target
790 real(rp) :: water_mass_ini
791
792 ! Trial-profile quantities
793 real(rp) :: energy_trial
794 real(rp) :: water_trial
795 real(rp) :: residual
796
797 ! Energy root bracket
798 real(rp) :: tbase_lo, tbase_mid, tbase_hi
799 real(rp) :: residual_lo, residual_hi
800
801 real(rp) :: water_tmin, energy_tmin
802 real(rp) :: water_wlo, water_whi
803 real(rp) :: energy_wlo, energy_whi
804
805 real(rp) :: energy_lo, energy_hi
806 real(rp) :: water_lo, water_hi
807
808 ! Water-feasible upper-bound search
809 real(rp) :: twater_lo, twater_mid, twater_hi
810 real(rp) :: water_mid, energy_mid
811
812 ! Work arrays
813 real(rp) :: temp_trial(npz,nez)
814 real(rp) :: pres_trial(npz,nez)
815 real(rp) :: qvap_trial(npz,nez)
816
817 ! Precipitation diagnostics
818 real(rp) :: water_mass_adj
819 real(rp) :: precip_mass_local
820 real(rp) :: precip_mass_total
821 real(rp) :: positive_cond_mass
822 real(rp) :: precip_scale
823
824 ! Tolerances
825 real(rp) :: energy_tol
826 real(rp) :: boundary_energy_tol
827 real(rp) :: water_tol
828
829 integer :: ke_z, pz
830 integer :: iter
831 integer :: iter_water
832
833 logical :: found_base
834 logical :: adiabat_profile_ok
835
836 logical :: water_lo_feasible
837 logical :: water_hi_feasible
838
839 real(rp), parameter :: mca_tbase_range = 40.0_rp
840 !------------------------------------------------------------
841
842 converged = .false.
843 profile_status = mca_profile_energy_maxiter
844
845 ! Initialize the adjusted profile with the original state
846
847 temp_adj(:,:) = temp(:,:)
848 qvap_adj(:,:) = qvap(:,:)
849 rhoqvap_adj(:,:) = dens(:,:) * qvap(:,:)
850 rhoprecip_adj(:,:) = 0.0_rp
851
852 !- Calculate initial column-integrated MSE
853
854 call integ_masked_column_mse( energy_target, & ! (out)
855 temp, qvap, zlev, dens, & ! (in)
856 int_weight, mix_mask, npz, nez ) ! (in)
857
858 energy_tol = mca_column_energy_atol + mca_column_energy_rtol * abs(energy_target)
859 boundary_energy_tol = mca_column_energy_atol + mca_boundary_energy_rtol * abs(energy_target)
860
861 !- Find temperature at the lowest adjusted node
862
863 tbase_mid = 0.0_rp
864 found_base = .false.
865
866 do ke_z = 1, nez
867 do pz = 1, npz
868 if ( mix_mask(pz,ke_z) ) then
869 tbase_mid = temp(pz,ke_z)
870 found_base = .true.
871 exit
872 end if
873 end do
874 if ( found_base ) exit
875 end do
876
877 if ( .not. found_base ) then
878 profile_status = mca_profile_no_adjusted_node
879 if ( debug_flag ) then
880 write(*,*) "solve_moist_neutral_adjustment: No adjusted DG node was found."
881 end if
882 return
883 end if
884
885 !- Calculate the total water mass in the column before adjustment
886
887 water_mass_ini = 0.0_rp
888
889 do ke_z = 1, nez
890 do pz = 1, npz
891 if ( mix_mask(pz,ke_z) ) then
892 water_mass_ini = water_mass_ini + int_weight(pz,ke_z) * dens(pz,ke_z) * qvap(pz,ke_z)
893 end if
894 end do
895 end do
896
897 water_tol = mca_water_atol + mca_water_rtol * max(abs(water_mass_ini),1.0_rp)
898
899 != Step 1:
900 ! Determine the upper limit of the water-feasible base temperature.
901 ! A trial moist-neutral profile is water-feasible when its vapor mass does not exceed the initial vapor mass in the adjustment region.
902
903 twater_lo = tbase_mid - mca_tbase_range
904 twater_hi = tbase_mid + mca_tbase_range
905
906 !- Evaluate the water mass at the lower bound of the temperature range
907
908 pres_trial(:,:) = pres(:,:)
909 call evaluate_trial_moist_neutral_profile( temp_trial, qvap_trial, water_tmin, energy_tmin, adiabat_profile_ok, & ! (out)
910 pres_trial, & ! (inout)
911 twater_lo, temp, qvap, dens, zlev, int_weight, mix_mask, saturation_mask, nez, npz, debug_flag ) ! (in)
912
913 if ( .not. adiabat_profile_ok ) then
914 profile_status = mca_profile_adiabat_failure
915 if ( debug_flag ) then
916 write(*,*) "solve_moist_neutral_adjustment: Moist-adiabat integration failed. tbase_lo=", twater_lo
917 end if
918 return
919 end if
920
921 water_lo_feasible = ( water_tmin <= water_mass_ini + water_tol )
922 if ( .not. water_lo_feasible ) then
923 profile_status = mca_profile_no_water_feasible_state
924
925 if ( debug_flag ) then
926 write(*,*) "No water-feasible saturated profile exists."
927 write(*,*) " tbase_lo=", twater_lo, "water_mass_lo=", water_tmin, "water_mass_ini =", water_mass_ini
928 end if
929 return
930 end if
931
932 ! Initialize the feasible side of the water-search bracket.
933 water_wlo = water_tmin
934 energy_wlo = energy_tmin
935
936
937 !- Evaluate the water mass at the upper bound of the temperature range
938
939 pres_trial(:,:) = pres(:,:)
940 call evaluate_trial_moist_neutral_profile( temp_trial, qvap_trial, water_whi, energy_whi, adiabat_profile_ok, & ! (out)
941 pres_trial, & ! (inout)
942 twater_hi, temp, qvap, dens, zlev, int_weight, mix_mask, saturation_mask, nez, npz, debug_flag ) ! (in)
943
944 if ( adiabat_profile_ok ) then
945 water_hi_feasible = ( water_whi <= water_mass_ini + water_tol )
946 else
947 water_hi_feasible = .false.
948 water_whi = huge(1.0_rp)
949
950 if ( debug_flag ) then
951 write(*,*) "High-temperature trial profile is not admissible."
952 write(*,*) " Treating it as the infeasible upper bracket: twater_hi=", twater_hi
953 end if
954 end if
955
956 !-
957
958 if ( debug_flag ) then
959 write(*,*) "Water feasibility at initial bounds:"
960 write(*,*) " t_lo, water_lo =", twater_lo, water_tmin
961 write(*,*) " t_hi, water_hi =", twater_hi, water_whi
962 write(*,*) " lower feasible =", water_lo_feasible
963 write(*,*) " upper feasible =", water_hi_feasible
964 write(*,*) " water_mass_ini =", water_mass_ini
965 write(*,*) " water_tol =", water_tol
966 end if
967
968
969 if ( .not. water_hi_feasible ) then
970 ! Search for the maximum base temperature for which the moist-adiabat construction succeeds
971 ! and the resulting vapor mass remains feasible.
972 ! The lower endpoint is always:
973 ! - thermodynamically admissible
974 ! - water feasible
975 !
976 ! The upper endpoint is either:
977 ! - water infeasible, or
978 ! - thermodynamically inadmissible
979
980 pres_trial(:,:) = pres(:,:)
981
982 do iter_water = 1, mca_max_water_iter
983 twater_mid = 0.5_rp * ( twater_lo + twater_hi )
984
985 call evaluate_trial_moist_neutral_profile( temp_trial, qvap_trial, water_mid, energy_mid, adiabat_profile_ok, & ! (out)
986 pres_trial, & ! (inout)
987 twater_mid, temp, qvap, dens, zlev, int_weight, mix_mask, saturation_mask, nez, npz, debug_flag ) ! (in)
988
989 if ( .not. adiabat_profile_ok ) then
990 ! Thermodynamically inadmissible midpoint:
991 ! move the upper boundary downward.
992 twater_hi = twater_mid
993 water_whi = huge(1.0_rp)
994 energy_whi = huge(1.0_rp)
995
996 ! if ( debug_flag ) then
997 ! write(*,*) "water iter=", iter_water
998 ! write(*,*) " inadmissible midpoint=", twater_mid, " new bracket=", twater_lo, twater_hi
999 ! end if
1000
1001 if ( abs(twater_hi - twater_lo) <= mca_temp_tol ) exit
1002 cycle
1003 end if
1004
1005 if ( water_mid <= water_mass_ini + water_tol ) then
1006 ! Admissible and water feasible.
1007 twater_lo = twater_mid
1008 water_wlo = water_mid
1009 energy_wlo = energy_mid
1010 else
1011 ! Admissible but water infeasible.
1012 twater_hi = twater_mid
1013 water_whi = water_mid
1014 energy_whi = energy_mid
1015 end if
1016
1017 ! if ( debug_flag ) then
1018 ! write(*,*) "water iter=", iter_water, ", tbase=", twater_lo, twater_mid, twater_hi, ", water=", water_wlo, water_mid, water_whi, ", target=", water_mass_ini
1019 ! end if
1020
1021 if ( abs(twater_hi - twater_lo) <= mca_temp_tol ) exit
1022 if ( abs(water_mid - water_mass_ini) <= water_tol ) exit
1023 end do
1024 end if
1025
1026 ! Lower endpoint of the energy root search.
1027 tbase_lo = tbase_mid - mca_tbase_range
1028 water_lo = water_tmin
1029 energy_lo = energy_tmin
1030
1031 if ( water_hi_feasible ) then
1032 ! Initial upper bound itself is water feasible.
1033 tbase_hi = twater_hi
1034 water_hi = water_whi
1035 energy_hi = energy_whi
1036 else
1037 ! The final feasible lower side of the water bracket
1038 ! is the maximum admissible/water-feasible base temperature.
1039 tbase_hi = twater_lo
1040 water_hi = water_wlo
1041 energy_hi = energy_wlo
1042 end if
1043
1044 !- Step 2:
1045 ! Evaluate the energy residual at both ends of the water-feasible interval.
1046
1047 residual_lo = energy_lo - energy_target
1048 residual_hi = energy_hi - energy_target
1049
1050 if ( debug_flag ) then
1051 write(*,*) "Energy-root feasibility check:"
1052 write(*,*) " energy_target =", energy_target
1053 write(*,*) " energy_tol =", energy_tol
1054 write(*,*) " tbase_lo =", tbase_lo
1055 write(*,*) " energy_lo =", energy_lo
1056 write(*,*) " residual_lo =", residual_lo
1057 write(*,*) " tbase_water_max =", tbase_hi
1058 write(*,*) " energy_water_max =", energy_hi
1059 write(*,*) " residual_water_max =", residual_hi
1060 write(*,*) " water_at_upper =", water_hi
1061 write(*,*) " water_mass_ini =", water_mass_ini
1062 end if
1063
1064 ! Check whether either endpoint is already an energy root.
1065 if ( abs(residual_lo) <= boundary_energy_tol ) then
1066 pres_trial(:,:) = pres(:,:)
1067 call evaluate_trial_moist_neutral_profile( temp_adj, qvap_adj, water_mass_adj, energy_trial, adiabat_profile_ok, & ! (out)
1068 pres_trial, & ! (inout)
1069 tbase_lo, temp, qvap, dens, zlev, int_weight, mix_mask, saturation_mask, nez, npz, debug_flag ) ! (in)
1070
1071 if ( .not. adiabat_profile_ok ) then
1072 profile_status = mca_profile_adiabat_failure
1073 return
1074 end if
1075 converged = .true.
1076 profile_status = mca_profile_success
1077
1078 else if ( abs(residual_hi) <= boundary_energy_tol ) then
1079 pres_trial(:,:) = pres(:,:)
1080 call evaluate_trial_moist_neutral_profile( temp_adj, qvap_adj, water_mass_adj, energy_trial, adiabat_profile_ok, & ! (out)
1081 pres_trial, & ! (inout)
1082 tbase_hi, temp, qvap, dens, zlev, int_weight, mix_mask, saturation_mask, nez, npz, debug_flag ) ! (in)
1083
1084 if ( .not. adiabat_profile_ok ) then
1085 profile_status = mca_profile_adiabat_failure
1086 return
1087 end if
1088
1089 converged = .true.
1090 profile_status = mca_profile_success
1091
1092 else if ( residual_lo * residual_hi > 0.0_rp ) then
1093 profile_status = mca_profile_no_energy_root
1094 if ( debug_flag ) then
1095 write(*,*) "No energy root exists in the water-feasible interval."
1096 write(*,*) " tbase_lo =", tbase_lo
1097 write(*,*) " tbase_water_max =", tbase_hi
1098 write(*,*) " residual_lo =", residual_lo
1099 write(*,*) " residual_upper =", residual_hi
1100 write(*,*) " water_mass_ini =", water_mass_ini
1101 write(*,*) " water_mass_upper =", water_hi
1102 end if
1103 return
1104 end if
1105
1106 !- Step 3:
1107 ! If the endpoint check found a valid bracket, solve the column-energy constraint by bisection.
1108
1109 if ( .not. converged ) then
1110
1111 pres_trial(:,:) = pres(:,:)
1112
1113 do iter=1, mca_max_energy_iter
1114
1115 tbase_mid = 0.5_rp * ( tbase_lo + tbase_hi )
1116
1117 call evaluate_trial_moist_neutral_profile( temp_adj, qvap_adj, water_trial, energy_trial, adiabat_profile_ok, & ! (out)
1118 pres_trial, & ! (inout)
1119 tbase_mid, temp, qvap, dens, zlev, int_weight, mix_mask, saturation_mask, nez, npz, debug_flag ) ! (in)
1120
1121 if ( .not. adiabat_profile_ok ) then
1122 profile_status = mca_profile_adiabat_failure
1123
1124 if ( debug_flag ) then
1125 write(*,*) "Moist-adiabat integration failed during"
1126 write(*,*) "energy-root search: iteration=", iter, ", tbase=", tbase_mid
1127 end if
1128 return
1129 end if
1130
1131 ! The energy search must remain inside the previously determined water-feasible interval.
1132
1133 if ( water_trial > water_mass_ini + water_tol ) then
1134 profile_status = mca_profile_no_energy_root
1135 if ( debug_flag ) then
1136 write(*,*) "Energy bisection entered a water-infeasible state."
1137 write(*,*) " tbase =", tbase_mid
1138 write(*,*) " water_trial =", water_trial
1139 write(*,*) " water_mass_ini =", water_mass_ini
1140 end if
1141 return
1142 end if
1143
1144 residual = energy_trial - energy_target
1145
1146 ! if ( debug_flag ) then
1147 ! write(*,*) "energy iter=", iter
1148 ! write(*,*) " tbase=", tbase_lo, tbase_mid, tbase_hi
1149 ! write(*,*) " energy_trial=", energy_trial, ", energy_target=", energy_target
1150 ! write(*,*) " residual=", residual
1151 ! write(*,*) " water_trial=", water_trial
1152 ! end if
1153
1154 if ( abs(residual) <= energy_tol ) then
1155 water_mass_adj = water_trial
1156 converged = .true.
1157 profile_status = mca_profile_success
1158 exit
1159 end if
1160
1161 ! General bisection update based on the residual signs.
1162
1163 if ( residual_lo * residual <= 0.0_rp ) then
1164 tbase_hi = tbase_mid
1165 residual_hi = residual
1166 else
1167 tbase_lo = tbase_mid
1168 residual_lo = residual
1169 end if
1170
1171 end do ! end loop for iteration
1172 end if
1173
1174 if ( .not. converged ) then
1175 profile_status = mca_profile_energy_maxiter
1176
1177 if ( debug_flag ) then
1178 write(*,*) "Energy bisection did not converge."
1179 write(*,*) " tbase_lo=", tbase_lo, ", tbase_hi=", tbase_hi
1180 write(*,*) " residual_lo=", residual_lo, ", residual_hi=", residual_hi
1181 write(*,*) " energy_tol=", energy_tol
1182 end if
1183 return
1184 end if
1185
1186 ! Step 4:
1187 ! Convert the converged vapor mixing ratio to density form and diagnose precipitation.
1188
1189 rhoqvap_adj(:,:) = dens(:,:) * qvap_adj(:,:)
1190
1191 precip_mass_total = max( 0.0_rp, water_mass_ini - water_mass_adj )
1192 positive_cond_mass = 0.0_rp
1193 do ke_z=1, nez
1194 do pz=1, npz
1195 if ( mix_mask(pz,ke_z) ) then
1196 precip_mass_local = max( 0.0_rp, dens(pz,ke_z) * qvap(pz,ke_z) - rhoqvap_adj(pz,ke_z) )
1197
1198 positive_cond_mass = positive_cond_mass + precip_mass_local * int_weight(pz,ke_z)
1199 end if
1200 end do
1201 end do
1202
1203 ! Distribute the column-integrated removed water mass in proportion to positive local vapor-density reductions.
1204
1205 if ( positive_cond_mass > eps ) then
1206 precip_scale = precip_mass_total / positive_cond_mass
1207 do ke_z=1, nez
1208 do pz=1, npz
1209 if ( mix_mask(pz,ke_z) ) then
1210 precip_mass_local = max( 0.0_rp, dens(pz,ke_z) * qvap(pz,ke_z) - rhoqvap_adj(pz,ke_z) )
1211 rhoprecip_adj(pz,ke_z) = precip_scale * precip_mass_local
1212 end if
1213 end do
1214 end do
1215 end if
1216
1217 return
1218 end subroutine solve_moist_neutral_adjustment
1219
1220!OCL SERIAL
1221 subroutine evaluate_trial_moist_neutral_profile( &
1222 temp_work, qvap_work, water_mass, energy, profile_ok, & ! (out)
1223 pres_work, & ! (inout)
1224 tbase, temp, qvap, dens, zlev, int_weight, & ! (in)
1225 adj_mask, forced_saturation_mask, nez, npz, debug_flag ) ! (in)
1226 implicit none
1227
1228 integer, intent(in) :: nez
1229 integer, intent(in) :: npz
1230 real(rp), intent(out) :: temp_work(npz,nez)
1231 real(rp), intent(out) :: qvap_work(npz,nez)
1232 real(rp), intent(out) :: water_mass
1233 real(rp), intent(out) :: energy
1234 logical, intent(out) :: profile_ok
1235 real(rp), intent(inout) :: pres_work(npz,nez)
1236 real(rp), intent(in) :: tbase
1237 real(rp), intent(in) :: temp(npz,nez)
1238 real(rp), intent(in) :: qvap(npz,nez)
1239 real(rp), intent(in) :: zlev(npz,nez)
1240 real(rp), intent(in) :: dens(npz,nez)
1241 real(rp), intent(in) :: int_weight(npz,nez)
1242 logical, intent(in) :: adj_mask(npz,nez)
1243 logical, intent(in) :: forced_saturation_mask(npz,nez)
1244 logical, intent(in) :: debug_flag
1245
1246 real(rp) :: qsat_work(npz,nez)
1247
1248real(rp) :: pres_new(npz,nez)
1249real(rp) :: qdry_work
1250real(rp) :: rtot_work
1251real(rp) :: pres_err
1252integer, parameter :: mca_max_pres_iter = 5
1253real(rp), parameter :: mca_pres_rtol = 1.0e-6_rp
1254integer :: iter_pres
1255
1256 integer :: ke_z, pz
1257 !------------------------------------------------
1258
1259 temp_work(:,:) = temp(:,:)
1260 qvap_work(:,:) = qvap(:,:)
1261 qsat_work(:,:) = 0.0_rp
1262
1263do iter_pres = 1, mca_max_pres_iter
1264! Reset trial fields before rebuilding the profile.
1265temp_work(:,:) = temp(:,:)
1266qvap_work(:,:) = qvap(:,:)
1267qsat_work(:,:) = 0.0_rp
1268! Preserve pressure outside adj_mask.
1269pres_new(:,:) = pres_work(:,:)
1270
1271 !- Evaluate the reference temperature profile over adj_mask.
1272
1273 call build_moist_adiabat_temperature( &
1274 temp_work, qsat_work, profile_ok, & ! (inout,out)
1275 tbase, pres_work, zlev, adj_mask, npz, nez, debug_flag ) ! (in)
1276
1277 if ( .not. profile_ok ) then
1278 water_mass = huge(1.0_rp)
1279 energy = huge(1.0_rp)
1280 return
1281 end if
1282
1283 !- Humidity construction:
1284 ! 1. Nearly saturated nodes in the diagnosed unstable core:
1285 ! qv = qsat.
1286 ! 2. Other nodes inside the adjustment region:
1287 ! retain the input qv unless the trial temperature produces supersaturation.
1288 ! 3. Outside the adjustment region:
1289 ! retain the input temperature and qv.
1290
1291 do ke_z = 1, nez
1292 do pz = 1, npz
1293 if ( adj_mask(pz,ke_z) ) then
1294 if ( forced_saturation_mask(pz,ke_z) ) then
1295 qvap_work(pz,ke_z) = qsat_work(pz,ke_z)
1296 else
1297 qvap_work(pz,ke_z) = min(qvap(pz,ke_z), qsat_work(pz,ke_z))
1298 end if
1299 end if
1300 qvap_work(pz,ke_z) = max(0.0_rp, qvap_work(pz,ke_z))
1301 end do
1302 end do
1303
1304
1305! 4. Pressure convergence check.
1306!------------------------------------------------------------
1307 pres_err = 0.0_rp
1308 do ke_z = 1, nez
1309 do pz = 1, npz
1310 if ( adj_mask(pz,ke_z) ) then
1311 qdry_work = 1.0_rp - qvap_work(pz,ke_z)
1312 rtot_work = rdry * qdry_work &
1313 + rvap * qvap_work(pz,ke_z)
1314 pres_new(pz,ke_z) = dens(pz,ke_z) &
1315 * rtot_work &
1316 * temp_work(pz,ke_z)
1317
1318 pres_err = max( pres_err, &
1319 abs( pres_new(pz,ke_z) - pres_work(pz,ke_z) ) &
1320 / max( abs(pres_work(pz,ke_z)), 1.0_rp ) )
1321 end if
1322 end do
1323 end do
1324
1325pres_work(:,:) = pres_new(:,:)
1326if ( pres_err <= mca_pres_rtol ) exit
1327if ( iter_pres == mca_max_pres_iter ) then
1328 profile_ok = .false.
1329 water_mass = huge(1.0_rp)
1330 energy = huge(1.0_rp)
1331 if ( debug_flag ) then
1332 write(*,*) "evaluate_trial_moist_neutral_profile: Pressure iteration failed to converge."
1333 write(*,*) " pres_err=", pres_err, "MCA_PRES_RTOL=", mca_pres_rtol
1334 end if
1335 return
1336end if
1337
1338end do
1339
1340 !- Column water over the full adjustment region.
1341
1342 water_mass = 0.0_rp
1343 do ke_z = 1, nez
1344 do pz = 1, npz
1345 if ( adj_mask(pz,ke_z) ) then
1346 water_mass = water_mass + int_weight(pz,ke_z) * dens(pz,ke_z) * qvap_work(pz,ke_z)
1347 end if
1348 end do
1349 end do
1350
1351 !- Column MSE over the full adjustment region.
1352
1353 call integ_masked_column_mse( energy, & ! (out)
1354 temp_work, qvap_work, zlev, dens, & ! (in)
1355 int_weight, adj_mask, npz, nez ) ! (in)
1356
1357 return
1358 end subroutine evaluate_trial_moist_neutral_profile
1359
1360 ! The column-MSE constraint is imposed while holding the density and pressure profiles fixed.
1361 ! Thermodynamic composition includes dry air and water vapor only.
1362 ! Condensate sensible heat and the energy carried away by precipitation are neglected.
1363!OCL SERIAL
1364 subroutine integ_masked_column_mse( energy, & ! (out)
1365 temp, qv, zlev, dens, & ! (in)
1366 int_weight, integ_mask, npz, nez ) ! (in)
1367 implicit none
1368 integer, intent(in) :: npz
1369 integer, intent(in) :: nez
1370 real(rp), intent(out) :: energy
1371 real(rp), intent(in) :: temp(npz,nez)
1372 real(rp), intent(in) :: qv(npz,nez)
1373 real(rp), intent(in) :: zlev(npz,nez)
1374 real(rp), intent(in) :: dens(npz,nez)
1375 real(rp), intent(in) :: int_weight(npz,nez)
1376 logical, intent(in) :: integ_mask(npz,nez)
1377
1378 integer :: ke_z, pz
1379 real(rp) :: qdry
1380 real(rp) :: cptot
1381 !------------------------------------------------
1382
1383 energy = 0.0_rp
1384
1385 do ke_z = 1, nez
1386 do pz = 1, npz
1387 if ( integ_mask(pz,ke_z) ) then
1388 qdry = 1.0_rp - qv(pz,ke_z) ! - qcon_(pz_,ke_z_)
1389
1390 cptot = cpdry * qdry &
1391 + cp_vapor * qv(pz,ke_z)
1392 ! Liquid-water sensible heat is neglected.
1393
1394 energy = energy + int_weight(pz,ke_z) * dens(pz,ke_z) * &
1395 ( cptot * temp(pz,ke_z) + grav * zlev(pz,ke_z) &
1396 + lhv * qv(pz,ke_z) )
1397 end if
1398 end do
1399 end do
1400 return
1401 end subroutine integ_masked_column_mse
1402
1403 !----
1404!OCL SERIAL
1405 subroutine build_moist_adiabat_temperature( &
1406 temp_profile, qsat_profile, profile_converged, & ! (out)
1407 tbase, pres, zlev, mask, npz, nez, debug_flag ) ! (in)
1408 implicit none
1409 integer, intent(in) :: npz
1410 integer, intent(in) :: nez
1411 real(rp), intent(inout) :: temp_profile(npz,nez)
1412 real(rp), intent(inout) :: qsat_profile(npz,nez)
1413 logical, intent(out) :: profile_converged
1414 real(rp), intent(in) :: tbase
1415 real(rp), intent(in) :: pres(npz,nez)
1416 real(rp), intent(in) :: zlev(npz,nez)
1417 logical, intent(in) :: mask(npz,nez)
1418 logical, intent(in) :: debug_flag
1419
1420 integer :: ke_z, pz
1421
1422 real(rp) :: t_prev, p_prev, z_prev
1423 real(rp) :: t_now, p_now, z_now
1424
1425 logical :: started
1426 logical :: is_converge_local
1427 !----------------------------------------------------
1428
1429 started = .false.
1430 profile_converged = .true.
1431
1432 do ke_z = 1, nez
1433 do pz = 1, npz
1434 if ( .not. mask(pz,ke_z) ) cycle
1435
1436 z_now = zlev(pz,ke_z)
1437 p_now = pres(pz,ke_z)
1438
1439 if ( .not. started ) then
1440 t_now = tbase
1441 started = .true.
1442 ! else if ( abs(z_now-z_prev) <= MCA_Z_TOL ) then
1443 ! t_now = t_prev
1444 else
1445 call solve_next_moist_adiabat_temp( t_now, is_converge_local, & ! (out)
1446 t_prev, p_prev, z_prev, p_now, z_now ) ! (in)
1447
1448 if ( .not. is_converge_local ) then
1449 profile_converged = .false.
1450 return
1451 end if
1452 end if
1453
1454 temp_profile(pz,ke_z) = t_now
1455 call atmos_saturation_pres2qsat_liq( t_now, p_now, & ! (in)
1456 qsat_profile(pz,ke_z) ) ! (out)
1457
1458 t_prev = t_now
1459 p_prev = p_now
1460 z_prev = z_now
1461 end do
1462 end do
1463
1464 return
1465 end subroutine build_moist_adiabat_temperature
1466
1467 !> Solve the temperature at (pres1,zlev1) such that the saturated MSE equals that at (temp0,pres0,zlev0).
1468!OCL SERIAL
1469 subroutine solve_next_moist_adiabat_temp( temp1, converged, & ! (out)
1470 temp0, pres0, zlev0, pres1, zlev1 ) ! (in)
1471 implicit none
1472 real(rp), intent(out) :: temp1
1473 logical, intent(out) :: converged
1474 real(rp), intent(in) :: temp0
1475 real(rp), intent(in) :: pres0
1476 real(rp), intent(in) :: zlev0
1477 real(rp), intent(in) :: pres1
1478 real(rp), intent(in) :: zlev1
1479
1480 real(rp) :: temp_lo, temp_mid, temp_hi
1481 real(rp) :: temp_cur, temp_new
1482 real(rp) :: f_lo, f_hi, f_cur
1483 real(rp) :: hs_target, hs_cur
1484 real(rp) :: dhs_dtemp
1485 real(rp) :: temp_tol, energy_tol
1486
1487 integer :: iter
1488
1489 integer, parameter :: max_iter = 60
1490 real(rp), parameter :: temp_range = 40.0_rp
1491
1492 logical :: use_newton
1493 !----------------------------------------------------
1494
1495 converged = .false.
1496
1497 call saturated_mse_point( temp0, pres0, zlev0, &
1498 hs_target )
1499
1500 temp_lo = temp0 - temp_range
1501 temp_hi = temp0 + temp_range
1502
1503 call saturated_mse_point( temp_lo, pres1, zlev1, &
1504 hs_cur )
1505 f_lo = hs_cur - hs_target
1506
1507 call saturated_mse_point( temp_hi, pres1, zlev1, &
1508 hs_cur )
1509 f_hi = hs_cur - hs_target
1510
1511 if ( f_lo * f_hi > 0.0_rp ) then
1512 return
1513 end if
1514
1515 energy_tol = mca_local_hmse_atol + mca_local_hmse_rtol * abs(hs_target)
1516 temp_tol = mca_temp_tol
1517
1518 temp_cur = min( max(temp0, temp_lo), temp_hi )
1519
1520 do iter = 1, max_iter
1521
1522 call saturated_mse_point_with_derivative( temp_cur, pres1, zlev1, &
1523 hs_cur, dhs_dtemp )
1524
1525 f_cur = hs_cur - hs_target
1526 if ( abs(f_cur) <= energy_tol ) then
1527 temp1 = temp_cur
1528 converged = .true.
1529 return
1530 end if
1531
1532 ! Update bracket using current function value.
1533 if ( f_lo * f_cur <= 0.0_rp ) then
1534 temp_hi = temp_cur
1535 f_hi = f_cur
1536 else
1537 temp_lo = temp_cur
1538 f_lo = f_cur
1539 end if
1540
1541 ! A sufficiently small bracket is also convergence.
1542 if ( abs(temp_hi - temp_lo) <= temp_tol ) then
1543 temp1 = 0.5_rp * (temp_lo + temp_hi)
1544 converged = .true.
1545 return
1546 end if
1547
1548 ! Safeguarded Newton step.
1549
1550 use_newton = .false.
1551 if ( abs(dhs_dtemp) > eps ) then
1552 temp_new = temp_cur - f_cur / dhs_dtemp
1553 if ( temp_new > temp_lo .and. temp_new < temp_hi ) then
1554 use_newton = .true.
1555 end if
1556 end if
1557
1558 if ( .not. use_newton ) then
1559 temp_new = 0.5_rp * (temp_lo + temp_hi)
1560 end if
1561
1562 temp_cur = temp_new
1563 end do
1564
1565 temp1 = temp_cur
1566 return
1567 contains
1568 subroutine saturated_mse_point( &
1569 temp, pres, zlev, hmse_sat )
1570 implicit none
1571 real(rp), intent(in) :: temp
1572 real(rp), intent(in) :: pres
1573 real(rp), intent(in) :: zlev
1574 real(rp), intent(out) :: hmse_sat
1575
1576 real(rp) :: qsat
1577 real(rp) :: cptot
1578 !------------------------------------------------
1579 call atmos_saturation_pres2qsat_liq( &
1580 temp, pres, qsat )
1581
1582 cptot = cpdry * ( 1.0_rp - qsat ) + cp_vapor * qsat
1583
1584 hmse_sat = cptot * temp + grav * zlev + lhv * qsat
1585 return
1586 end subroutine saturated_mse_point
1587 end subroutine solve_next_moist_adiabat_temp
1588
1589 subroutine saturated_mse_point_with_derivative( &
1590 temp, pres, zlev, hmse_sat, dhmse_dtemp )
1591 use scale_atmos_saturation, only: atmos_saturation_dqs_dtem_dpre_liq
1592 implicit none
1593 real(rp), intent(in) :: temp
1594 real(rp), intent(in) :: pres
1595 real(rp), intent(in) :: zlev
1596 real(rp), intent(out) :: hmse_sat
1597 real(rp), intent(out) :: dhmse_dtemp
1598
1599 real(rp) :: qsat
1600 real(rp) :: dqsat_dtemp
1601 real(rp) :: dqsat_dpres
1602 real(rp) :: cptot
1603 real(rp) :: qdry_dummy
1604 !--------------------------------------------------
1605
1606 call atmos_saturation_pres2qsat_liq( temp, pres, &
1607 qsat )
1608 call atmos_saturation_dqs_dtem_dpre_liq( temp, pres, qdry_dummy, &
1609 dqsat_dtemp, dqsat_dpres )
1610
1611 cptot = cpdry * (1.0_rp - qsat) &
1612 + cp_vapor * qsat
1613
1614 hmse_sat = cptot * temp + grav * zlev + lhv * qsat
1615
1616 ! h_s = cp(qs) T + gz + Lv qs
1617 ! where cp(qs) = Cp_d + (Cp_v-Cp_d) qs
1618 !
1619 ! * dh_s/dT = cp(qs) + T (Cp_v-Cp_d) dqs/dT + Lv dqs/dT
1620
1621 dhmse_dtemp = cptot &
1622 + ( ( cp_vapor - cpdry ) * temp + lhv ) * dqsat_dtemp
1623 return
1624 end subroutine saturated_mse_point_with_derivative
1626
module FElib / Atmosphere / Physics cumulus parameterization
subroutine, public atm_phy_cp_dgm_mconv_adjustment_calc_tendency(dens_t, rhot_t, rhoqv_t, sflx_rain, sflx_engi, ddens, drhot, qv, pt, pres, dens_hyd, rtot, cptot, dtsec, lmesh, elem, elem1d)
Calculate tendencies with moist convective adjustment scheme.
subroutine, public atm_phy_cp_dgm_mconv_adjustment_setup()
Setup a module for moist convective adjustment scheme.
subroutine, public atm_phy_cp_dgm_mconv_adjustment_finalize()
Finalize a module for moist convective adjustment scheme.
module FElib / Element / Base
module FElib / Mesh / Local 2D
module FElib / Mesh / Local 3D
Derived type representing a 1D reference element.
Derived type representing a 2D reference element.
Derived type representing a 3D reference element.
Derived type representing a local mesh for 2D domain.
Derived type to manage a local 3D computational domain.