11module gravity_waves_sources
22 use derivative_mod, only : derivative_t
3- use dimensions_mod, only : np,nlev
3+ use dimensions_mod, only : np,nlev,nlevp
44 use edgetype_mod, only : EdgeBuffer_t
55 use element_mod, only : element_t
66 use hybrid_mod, only : hybrid_t
@@ -38,7 +38,7 @@ subroutine gws_init(elem)
3838
3939 end subroutine gws_init
4040 !- ------------------------------------------------------------------------------------------------
41- subroutine gws_src_fnct (elem , tl , nphys , frontgf , frontga )
41+ subroutine gws_src_fnct (elem , tl , nphys , use_fgf_pgrad_correction , use_fgf_zgrad_correction , frontgf , frontga )
4242 use dimensions_mod, only : npsq, nelemd
4343 use dof_mod, only : UniquePoints
4444 use dyn_comp, only : dom_mt
@@ -51,6 +51,8 @@ subroutine gws_src_fnct(elem, tl, nphys, frontgf, frontga)
5151 type (element_t), intent (inout ), dimension (:) :: elem
5252 integer , intent (in ) :: tl
5353 integer , intent (in ) :: nphys
54+ logical , intent (in ) :: use_fgf_pgrad_correction
55+ logical , intent (in ) :: use_fgf_zgrad_correction
5456 real (kind= real_kind), intent (out ) :: frontgf(nphys* nphys,pver,nelemd)
5557 real (kind= real_kind), intent (out ) :: frontga(nphys* nphys,pver,nelemd)
5658
@@ -67,7 +69,10 @@ subroutine gws_src_fnct(elem, tl, nphys, frontgf, frontga)
6769 hybrid = hybrid_create(par,ithr,hthreads)
6870 allocate (frontgf_thr(nphys,nphys,nlev,nets:nete))
6971 allocate (frontga_thr(nphys,nphys,nlev,nets:nete))
70- call compute_frontogenesis(frontgf_thr,frontga_thr,tl,elem,hybrid,nets,nete,nphys)
72+ call compute_frontogenesis( frontgf_thr, frontga_thr, tl, &
73+ use_fgf_pgrad_correction, &
74+ use_fgf_zgrad_correction, &
75+ elem, hybrid, nets, nete, nphys )
7176 if (fv_nphys> 0 ) then
7277 do ie = nets,nete
7378 do k = 1 ,nlev
@@ -88,7 +93,10 @@ subroutine gws_src_fnct(elem, tl, nphys, frontgf, frontga)
8893
8994 end subroutine gws_src_fnct
9095 !- ------------------------------------------------------------------------------------------------
91- subroutine compute_frontogenesis (frontgf ,frontga ,tl ,elem ,hybrid ,nets ,nete ,nphys )
96+ subroutine compute_frontogenesis ( frontgf , frontga , tl , &
97+ use_fgf_pgrad_correction , &
98+ use_fgf_zgrad_correction , &
99+ elem , hybrid , nets , nete , nphys )
92100 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
93101 ! compute frontogenesis function F
94102 ! F = -gradth dot C
@@ -99,63 +107,180 @@ subroutine compute_frontogenesis(frontgf,frontga,tl,elem,hybrid,nets,nete,nphys)
99107 !
100108 ! Original by Mark Taylor, July 2011
101109 ! Change by Santos, 10 Aug 2011:
102- ! Integrated into gravity_waves_sources module, several arguments made global
103- ! to prevent repeated allocation/initialization
110+ ! Integrated into gravity_waves_sources module, several arguments made global
111+ ! to prevent repeated allocation/initialization
104112 ! Change by Aaron Donahue, April 2017:
105113 ! Fixed bug where boundary information was called for processors not associated
106114 ! with dynamics when dyn_npes<npes
115+ ! Change by Walter Hannah, Oct 2025:
116+ ! added pressure gradient correction
107117 !
108118 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
109- use physical_constants, only: kappa
119+ use physical_constants, only: kappa, g
110120 use derivative_mod, only: gradient_sphere, ugradv_sphere
111- use edge_mod, only : edge_g, edgevpack_nlyr, edgevunpack_nlyr
121+ use edge_mod, only: edge_g, edgevpack_nlyr, edgevunpack_nlyr
112122 use bndry_mod, only: bndry_exchangev
113123 use dyn_comp, only: hvcoord
114124 use spmd_utils, only: iam
115125 use parallel_mod, only: par
116- use element_ops, only : get_temperature
126+ use element_ops, only: get_temperature, get_hydro_pressure_i, get_phi
117127 use dyn_grid, only: fv_nphys
118- use prim_driver_mod, only : deriv1
119- use element_ops, only : get_temperature
120- use gllfvremap_mod, only : gfr_g2f_scalar, gfr_g2f_vector
128+ use prim_driver_mod, only: deriv1
129+ use gllfvremap_mod, only: gfr_g2f_scalar, gfr_g2f_vector
121130 implicit none
122131 type (hybrid_t), intent (in ) :: hybrid
123132 type (element_t),target ,intent (inout ) :: elem(:)
124133 integer , intent (in ) :: nets,nete,nphys
125134 integer , intent (in ) :: tl ! timelevel to use
135+ logical , intent (in ) :: use_fgf_pgrad_correction
136+ logical , intent (in ) :: use_fgf_zgrad_correction
126137 real (kind= real_kind), intent (out ) :: frontgf(nphys,nphys,nlev,nets:nete)
127138 real (kind= real_kind), intent (out ) :: frontga(nphys,nphys,nlev,nets:nete)
128139
129140 ! local
130141 integer :: k,kptr,i,j,ie,component
131142 real (kind= real_kind) :: frontgf_gll(np,np,nlev,nets:nete)
132143 real (kind= real_kind) :: gradth_gll(np,np,2 ,nlev,nets:nete) ! grad(theta)
133- real (kind= real_kind) :: p(np,np) ! pressure at mid points
134- real (kind= real_kind) :: theta(np,np) ! potential temperature at mid points
135- real (kind= real_kind) :: temperature(np,np,nlev) ! Temperature
144+ real (kind= real_kind) :: zint(np,np,nlevp) ! interface altitude
145+ real (kind= real_kind) :: zmid(np,np,nlev) ! mid-point altitude
146+ real (kind= real_kind) :: pint(np,np,nlevp) ! interface hydrostatic pressure
147+ real (kind= real_kind) :: pmid(np,np,nlev) ! mid-point hydrostatic pressure
148+ real (kind= real_kind) :: temperature(np,np,nlev) ! Temperature
136149 real (kind= real_kind) :: C(np,np,2 ), wf1(nphys* nphys,nlev), wf2(nphys* nphys,nlev)
150+
151+ real (kind= real_kind) :: theta(np,np,nlev) ! potential temperature at mid points
152+ real (kind= real_kind) :: dum_grad(np,np,2 ) ! temporary variable for spherical gradient calcualtions
153+ real (kind= real_kind) :: dum_cart(np,np,3 ,nlev) ! temporary variable for cartesian gradient calcualtions
154+
155+ ! variables needed for eta to pressure surface correction
156+ real (kind= real_kind) :: grad_vert_gll(np,np,2 ,nlev) ! grad(vertical coordinate) - either pressure or geopotential
157+ real (kind= real_kind) :: theta_dvert(np,np,nlev) ! d(theta)/dp for eta to pressure surface correction
158+ real (kind= real_kind) :: dum_cart_dvert(np,np,3 ,nlev)! vertical pressure derivative of dum_cart
159+
160+ !- --------------------------------------------------------------------------
161+ !
162+ ! For a vector velocity "v", a tensor "grad(v)", and a vector "grad(theta)",
163+ ! this loop computes the vector "grad(theta)*grad(v)"
164+ !
165+ ! Representing the tensor "grad(v)" in spherical coordinates is difficult.
166+ ! This routine avoids this by computing a mathematically equivalent form
167+ ! using a mixture of Cartesian and spherical coordinates
168+ !
169+ ! This routine is a modified version of derivative_mod.F90:ugradv_sphere()
170+ ! in that the grad(v) term is modified to compute grad_z(v) (or grad_p(v))
171+ ! - the gradient on z-surfaces expressed in terms of the gradient on model
172+ ! surfaces and a vertical geopotential gradient.
173+ !
174+ ! The old version only computed gradients on model surfaces, which creates
175+ ! issues around topograpy. This is address with use_fgf_pgrad_correction=.true.
176+ !
177+ ! First, v is represented in cartesian coordinates v(c) for c=1,2,3
178+ ! For each v(c), we compute its gradient on z (or p) surfaces via:
179+ ! grad(v(c)) - d(v(c))/dz grad(z)
180+ ! Each of these gradients is represented in *spherical* coordinates (i=1,2)
181+ !
182+ ! We then dot each of these vectors with grad(theta). This dot product is
183+ ! computed in spherical coordinates. The end result is dum_cart(c),
184+ ! for c=1,2,3 These three scalars are the three Cartesian coefficients of
185+ ! the vector "grad(theta)*grad(v)"
186+ !
187+ ! This Cartesian vector is then transformed back to spherical coordinates
188+ !
137189 !- --------------------------------------------------------------------------
138190
139191 do ie = nets,nete
140- do k = 1 ,nlev
141- ! pressure at mid points
142- p(:,:) = hvcoord% hyam(k)* hvcoord% ps0 + hvcoord% hybm(k)* elem(ie)% state% ps_v(:,:,tl)
143- ! potential temperature: theta = T (p/p0)^kappa
192+
193+ if (use_fgf_pgrad_correction .or. use_fgf_zgrad_correction) then
194+
195+ ! compute pressure at interfaces and mid-points
196+ call get_hydro_pressure_i(pint,elem(ie)% state% dp3d(:,:,:,tl),hvcoord)
197+
144198 call get_temperature(elem(ie),temperature,hvcoord,tl)
145- theta(:,:) = temperature(:,:,k)* (psurf_ref / p(:,:))** kappa
146- gradth_gll(:,:,:,k,ie) = gradient_sphere(theta,deriv1,elem(ie)% Dinv)
147- ! compute C = (grad(theta) dot grad ) u
148- C(:,:,:) = ugradv_sphere(gradth_gll(:,:,:,k,ie), elem(ie)% state% v(:,:,:,k,tl),deriv1,elem(ie))
149- ! gradth_gll dot C
150- frontgf_gll(:,:,k,ie) = - ( C(:,:,1 )* gradth_gll(:,:,1 ,k,ie) + C(:,:,2 )* gradth_gll(:,:,2 ,k,ie) )
151- ! apply mass matrix
152- gradth_gll(:,:,1 ,k,ie) = gradth_gll(:,:,1 ,k,ie)* elem(ie)% spheremp(:,:)
153- gradth_gll(:,:,2 ,k,ie) = gradth_gll(:,:,2 ,k,ie)* elem(ie)% spheremp(:,:)
154- frontgf_gll(:,:,k,ie) = frontgf_gll(:,:,k,ie)* elem(ie)% spheremp(:,:)
155- end do ! k
199+
200+ ! calculate pmid and potential temperature: theta = T (p/p0)^kappa
201+ do k = 1 ,nlev
202+ pmid(:,:,k) = pint(:,:,k) + elem(ie)% state% dp3d(:,:,k,tl)/ 2
203+ theta(:,:,k) = temperature(:,:,k)* (psurf_ref / pmid(:,:,k))** kappa
204+ end do
205+
206+ if (use_fgf_pgrad_correction) then
207+ ! compute d(theta)/dp
208+ call compute_vertical_derivative(pint,theta,theta_dvert)
209+ end if
210+
211+ if (use_fgf_zgrad_correction) then
212+ ! compute geopotential
213+ call get_phi(elem(ie), zmid, zint, hvcoord, tl)
214+ ! compute d(theta)/dz
215+ call compute_vertical_derivative(zint,theta,theta_dvert)
216+ end if
217+
218+ do k = 1 ,nlev
219+ gradth_gll(:,:,:,k,ie) = gradient_sphere(theta(:,:,k),deriv1,elem(ie)% Dinv)
220+ if (use_fgf_pgrad_correction) grad_vert_gll(:,:,:,k) = gradient_sphere(pmid(:,:,k),deriv1,elem(ie)% Dinv)
221+ if (use_fgf_zgrad_correction) grad_vert_gll(:,:,:,k) = gradient_sphere(zmid(:,:,k),deriv1,elem(ie)% Dinv)
222+ do component= 1 ,2
223+ gradth_gll(:,:,component,k,ie) = gradth_gll(:,:,component,k,ie) - theta_dvert(:,:,k) * grad_vert_gll(:,:,component,k)
224+ end do
225+ end do
226+
227+ do k = 1 ,nlev
228+ ! latlon -> cartesian - Summing along the third dimension is a sum over components for each point
229+ do component= 1 ,3
230+ dum_cart(:,:,component,k)= sum ( elem(ie)% vec_sphere2cart(:,:,component,:) * elem(ie)% state% v(:,:,:,k,tl) ,3 )
231+ end do
232+ end do
233+
234+ do component= 1 ,3
235+ if (use_fgf_pgrad_correction) call compute_vertical_derivative(pint,dum_cart(:,:,component,:),dum_cart_dvert(:,:,component,:))
236+ if (use_fgf_zgrad_correction) call compute_vertical_derivative(zint,dum_cart(:,:,component,:),dum_cart_dvert(:,:,component,:))
237+ end do
238+
239+ do k = 1 ,nlev
240+ ! Do ugradv on the cartesian components - Dot u with the gradient of each component
241+ do component= 1 ,3
242+ dum_grad(:,:,:) = gradient_sphere(dum_cart(:,:,component,k),deriv1,elem(ie)% Dinv)
243+ do i= 1 ,2
244+ dum_grad(:,:,i) = dum_grad(:,:,i) - dum_cart_dvert(:,:,component,k) * grad_vert_gll(:,:,i,k)
245+ end do
246+ dum_cart(:,:,component,k) = sum ( gradth_gll(:,:,:,k,ie) * dum_grad ,3 )
247+ enddo
248+ ! cartesian -> latlon - vec_sphere2cart is its own pseudoinverse.
249+ do i= 1 ,2
250+ C(:,:,i) = sum (dum_cart(:,:,:,k)* elem(ie)% vec_sphere2cart(:,:,:,i), 3 )
251+ end do
252+ ! gradth_gll dot C
253+ frontgf_gll(:,:,k,ie) = - ( C(:,:,1 )* gradth_gll(:,:,1 ,k,ie) + C(:,:,2 )* gradth_gll(:,:,2 ,k,ie) )
254+ ! apply mass matrix
255+ gradth_gll(:,:,1 ,k,ie) = gradth_gll(:,:,1 ,k,ie)* elem(ie)% spheremp(:,:)
256+ gradth_gll(:,:,2 ,k,ie) = gradth_gll(:,:,2 ,k,ie)* elem(ie)% spheremp(:,:)
257+ frontgf_gll(:,:,k,ie) = frontgf_gll(:,:,k,ie)* elem(ie)% spheremp(:,:)
258+ end do ! k
259+
260+ else ! .not. use_fgf_pgrad_correction
261+
262+ do k = 1 ,nlev
263+ ! pressure at mid points - this pressure preserves the old behavior of E3SMv3 and prior
264+ pmid(:,:,k) = hvcoord% hyam(k)* hvcoord% ps0 + hvcoord% hybm(k)* elem(ie)% state% ps_v(:,:,tl)
265+ ! potential temperature: theta = T (p/p0)^kappa
266+ call get_temperature(elem(ie),temperature,hvcoord,tl)
267+ theta(:,:,k) = temperature(:,:,k)* (psurf_ref / pmid(:,:,k))** kappa
268+ gradth_gll(:,:,:,k,ie) = gradient_sphere(theta(:,:,k),deriv1,elem(ie)% Dinv)
269+ ! compute C = (grad(theta) dot grad ) u
270+ C(:,:,:) = ugradv_sphere(gradth_gll(:,:,:,k,ie), elem(ie)% state% v(:,:,:,k,tl),deriv1,elem(ie))
271+ ! gradth_gll dot C
272+ frontgf_gll(:,:,k,ie) = - ( C(:,:,1 )* gradth_gll(:,:,1 ,k,ie) + C(:,:,2 )* gradth_gll(:,:,2 ,k,ie) )
273+ ! apply mass matrix
274+ gradth_gll(:,:,1 ,k,ie) = gradth_gll(:,:,1 ,k,ie)* elem(ie)% spheremp(:,:)
275+ gradth_gll(:,:,2 ,k,ie) = gradth_gll(:,:,2 ,k,ie)* elem(ie)% spheremp(:,:)
276+ frontgf_gll(:,:,k,ie) = frontgf_gll(:,:,k,ie)* elem(ie)% spheremp(:,:)
277+ end do ! k
278+
279+ end if ! use_fgf_pgrad_correction
280+
156281 ! pack
157- call edgeVpack_nlyr(edge_g, elem(ie)% desc, frontgf_gll(:,:,:,ie),nlev,0 ,3 * nlev)
158- call edgeVpack_nlyr(edge_g, elem(ie)% desc, gradth_gll(:,:,:,:,ie),2 * nlev,nlev,3 * nlev)
282+ call edgeVpack_nlyr(edge_g, elem(ie)% desc, frontgf_gll(:,:,:,ie),nlev,0 ,3 * nlev)
283+ call edgeVpack_nlyr(edge_g, elem(ie)% desc, gradth_gll(:,:,:,:,ie),2 * nlev,nlev,3 * nlev)
159284
160285 end do ! ie
161286
@@ -192,4 +317,37 @@ subroutine compute_frontogenesis(frontgf,frontga,tl,elem,hybrid,nets,nete,nphys)
192317
193318 end subroutine compute_frontogenesis
194319 !- ------------------------------------------------------------------------------------------------
320+ subroutine compute_vertical_derivative (vert_int , data_mid , ddata_dvert )
321+ !- --------------------------------------------------------------------------
322+ real (kind= real_kind), intent (in ) :: vert_int(np,np,nlev) ! vertical coord on interfaces (i.e. pint or zint)
323+ real (kind= real_kind), intent (in ) :: data_mid(np,np,nlev) ! input data in mid-points
324+ real (kind= real_kind), intent (out ) :: ddata_dvert(np,np,nlev) ! vertical derivative of data
325+ !- --------------------------------------------------------------------------
326+ integer :: k
327+ real (kind= real_kind) :: vert_above(np,np) ! pressure interpolated to interface above the current k mid-point
328+ real (kind= real_kind) :: vert_below(np,np) ! pressure interpolated to interface below the current k mid-point
329+ real (kind= real_kind) :: data_above(np,np) ! data interpolated to interface above the current k mid-point
330+ real (kind= real_kind) :: data_below(np,np) ! data interpolated to interface below the current k mid-point
331+ !- --------------------------------------------------------------------------
332+ do k = 1 ,nlev
333+ if (k== 1 ) then
334+ data_above = data_mid(:,:,k)
335+ data_below = ( data_mid(:,:,k+1 ) + data_mid(:,:,k) ) / 2.0 ! interpolate to interface k+1
336+ vert_above = ( vert_int(:,:,k+1 ) + vert_int(:,:,k) ) / 2.0 ! interpolate to mid-point k
337+ vert_below = vert_int(:,:,k+1 )
338+ elseif (k== nlev) then
339+ data_above = ( data_mid(:,:,k-1 ) + data_mid(:,:,k) ) / 2.0 ! interpolate to interface
340+ data_below = data_mid(:,:,k)
341+ vert_above = vert_int(:,:,k)
342+ vert_below = ( vert_int(:,:,k+1 ) + vert_int(:,:,k) ) / 2.0 ! interpolate to mid-point k
343+ else
344+ data_above = ( data_mid(:,:,k-1 ) + data_mid(:,:,k) ) / 2.0 ! interpolate to interface k
345+ data_below = ( data_mid(:,:,k+1 ) + data_mid(:,:,k) ) / 2.0 ! interpolate to interface k+1
346+ vert_above = vert_int(:,:,k)
347+ vert_below = vert_int(:,:,k+1 )
348+ end if
349+ ddata_dvert(:,:,k) = ( data_above - data_below ) / ( vert_above - vert_below )
350+ end do
351+ end subroutine compute_vertical_derivative
352+ !- ------------------------------------------------------------------------------------------------
195353end module gravity_waves_sources
0 commit comments