Recon1d_PPM_H4_2019.F90
1! This file is part of MOM6, the Modular Ocean Model version 6.
2! See the LICENSE file for licensing information.
3! SPDX-License-Identifier: Apache-2.0
4
5!> Piecewise Parabolic Method 1D reconstruction with h4 interpolation for edges
6!!
7!! This implementation of PPM follows White and Adcroft 2008 \cite white2008, with cells
8!! resorting to PCM for extrema including first and last cells in column.
9!! This scheme differs from Colella and Woodward, 1984 \cite colella1984, in the method
10!! of first estimating the fourth-order accurate edge values.
11!! This uses numerical expressions refactored at the beginning of 2019.
12!! The first and last cells are always limited to PCM.
13module recon1d_ppm_h4_2019
14
15use recon1d_type, only : recon1d, testing
16
17implicit none ; private
18
19public ppm_h4_2019, testing
20
21!> PPM reconstruction following White and Adcroft, 2008
22!!
23!! The source for the methods ultimately used by this class are:
24!! - init() *locally defined
25!! - reconstruct() *locally defined
26!! - average() *locally defined
27!! - f() *locally defined
28!! - dfdx() *locally defined
29!! - check_reconstruction() *locally defined
30!! - unit_tests() *locally defined
31!! - destroy() *locally defined
32!! - remap_to_sub_grid() -> recon1d_type.remap_to_sub_grid()
33!! - init_parent() -> init()
34!! - reconstruct_parent() -> reconstruct()
35type, extends (recon1d) :: ppm_h4_2019
36
37 real, allocatable :: ul(:) !< Left edge value [A]
38 real, allocatable :: ur(:) !< Right edge value [A]
39
40contains
41 !> Implementation of the PPM_H4_2019 initialization
43 !> Implementation of the PPM_H4_2019 reconstruction
44 procedure :: reconstruct => reconstruct
45 !> Implementation of the PPM_H4_2019 average over an interval [A]
47 !> Implementation of evaluating the PPM_H4_2019 reconstruction at a point [A]
49 !> Implementation of the derivative of the PPM_H4_2019 reconstruction at a point [A]
51 !> Implementation of deallocation for PPM_H4_2019
53 !> Implementation of check reconstruction for the PPM_H4_2019 reconstruction
54 procedure :: check_reconstruction => check_reconstruction
55 !> Implementation of unit tests for the PPM_H4_2019 reconstruction
56 procedure :: unit_tests => unit_tests
57
58 !> Duplicate interface to init()
59 procedure :: init_parent => init
60 !> Duplicate interface to reconstruct()
61 procedure :: reconstruct_parent => reconstruct
62
63end type ppm_h4_2019
64
65contains
66
67!> Initialize a 1D PPM_H4_2019 reconstruction for n cells
68subroutine init(this, n, h_neglect, check)
69 class(ppm_h4_2019), intent(out) :: this !< This reconstruction
70 integer, intent(in) :: n !< Number of cells in this column
71 real, optional, intent(in) :: h_neglect !< A negligibly small width used in cell reconstructions [H]
72 logical, optional, intent(in) :: check !< If true, enable some consistency checking
73
74 this%n = n
75
76 allocate( this%u_mean(n) )
77 allocate( this%ul(n) )
78 allocate( this%ur(n) )
79
80 this%h_neglect = tiny( this%u_mean(1) )
81 if (present(h_neglect)) this%h_neglect = h_neglect
82 this%check = .false.
83 if (present(check)) this%check = check
84
85end subroutine init
86
87!> Calculate a 1D PPM_H4_2019 reconstructions based on h(:) and u(:)
88subroutine reconstruct(this, h, u)
89 class(ppm_h4_2019), intent(inout) :: this !< This reconstruction
90 real, intent(in) :: h(*) !< Grid spacing (thickness) [typically H]
91 real, intent(in) :: u(*) !< Cell mean values [A]
92 ! Local variables
93 real :: slp ! The PLM slopes (difference across cell) [A]
94 real :: sigma_l, sigma_c, sigma_r ! Left, central and right slope estimates as
95 ! differences across the cell [A]
96 real :: u_min, u_max ! Minimum and maximum value across cell [A]
97 real :: u_l, u_r, u_c ! Left, right, and center values [A]
98 real :: h_l, h_c, h_r ! Thickness of left, center and right cells [H]
99 real :: h_c0 ! Thickness of center with h_neglect added [H]
100 real :: h0, h1, h2, h3 ! temporary thicknesses [H]
101 real :: h_min ! A minimal cell width [H]
102 real :: f1 ! An auxiliary variable [H]
103 real :: f2 ! An auxiliary variable [A H]
104 real :: f3 ! An auxiliary variable [H-1]
105 real :: et1, et2, et3 ! terms the expression for edge values [A H]
106 real :: I_h12 ! The inverse of the sum of the two central thicknesses [H-1]
107 real :: I_h012, I_h123 ! Inverses of sums of three successive thicknesses [H-1]
108 real :: I_den_et2, I_den_et3 ! Inverses of denominators in edge value terms [H-2]
109 real :: dx ! Difference of successive values of x [H]
110 real :: f ! value of polynomial at x in arbitrary units [A]
111 real :: edge_l, edge_r ! Edge values (left and right) [A]
112 real :: expr1, expr2 ! Temporary expressions [A2]
113 real :: slope_x_h ! retained PLM slope times half grid step [A]
114 real :: u0_avg ! avg value at given edge [A]
115 real, parameter :: hMinFrac = 1.e-5 !< A minimum fraction for min(h)/sum(h) [nondim]
116 real :: edge_values(this%n,2) ! Edge values [A]
117 real :: ppoly_coef(this%n,3) ! Polynomial coefficients [A]
118 real :: dz(4) ! A temporary array of limited layer thicknesses [H]
119 real :: u_tmp(4) ! A temporary array of cell average properties [A]
120 real :: A(4,4) ! Differences in successive positions raised to various powers,
121 ! in units that vary with the second (j) index as [H^j]
122 real :: B(4) ! The right hand side of the system to solve for C [A H]
123 real :: C(4) ! The coefficients of a fit polynomial in units that vary
124 ! with the index (j) as [A H^(j-1)]
125 integer :: k, n, km1, kp1
126
127 n = this%n
128
129 ! Loop on interior cells
130 do k = 3, n-1
131
132 h0 = h(k-2)
133 h1 = h(k-1)
134 h2 = h(k)
135 h3 = h(k+1)
136
137 ! Avoid singularities when consecutive pairs of h vanish
138 if (h0+h1==0.0 .or. h1+h2==0.0 .or. h2+h3==0.0) then
139 h_min = hminfrac*max( this%h_neglect, (h0+h1)+(h2+h3) )
140 h0 = max( h_min, h0 )
141 h1 = max( h_min, h1 )
142 h2 = max( h_min, h2 )
143 h3 = max( h_min, h3 )
144 endif
145
146 i_h12 = 1.0 / (h1+h2)
147 i_den_et2 = 1.0 / ( ((h0+h1)+h2)*(h0+h1) ) ; i_h012 = (h0+h1) * i_den_et2
148 i_den_et3 = 1.0 / ( (h1+(h2+h3))*(h2+h3) ) ; i_h123 = (h2+h3) * i_den_et3
149
150 et1 = ( 1.0 + (h1 * i_h012 + (h0+h1) * i_h123) ) * i_h12 * (h2*(h2+h3)) * u(k-1) + &
151 ( 1.0 + (h2 * i_h123 + (h2+h3) * i_h012) ) * i_h12 * (h1*(h0+h1)) * u(k)
152 et2 = ( h1 * (h2*(h2+h3)) * i_den_et2 ) * (u(k-1)-u(k-2))
153 et3 = ( h2 * (h1*(h0+h1)) * i_den_et3 ) * (u(k) - u(k+1))
154 edge_values(k,1) = (et1 + (et2 + et3)) / ((h0 + h1) + (h2 + h3))
155 edge_values(k-1,2) = edge_values(k,1)
156
157 enddo ! end loop on interior cells
158
159 ! Determine first two edge values
160 do k=1,4 ; dz(k) = max(this%h_neglect, h(k) ) ; u_tmp(k) = u(k) ; enddo
161 call end_value_h4(dz, u_tmp, c)
162
163 ! Set the edge values of the first cell
164 edge_values(1,1) = c(1)
165 edge_values(1,2) = c(1) + dz(1) * ( c(2) + dz(1) * ( c(3) + dz(1) * c(4) ) )
166 edge_values(2,1) = edge_values(1,2)
167
168 ! Determine two edge values of the last cell
169 do k=1,4 ; dz(k) = max(this%h_neglect, h(n+1-k) ) ; u_tmp(k) = u(n+1-k) ; enddo
170 call end_value_h4(dz, u_tmp, c)
171
172 ! Set the last and second to last edge values
173 edge_values(n,2) = c(1)
174 edge_values(n,1) = c(1) + dz(1) * ( c(2) + dz(1) * ( c(3) + dz(1) * c(4) ) )
175 edge_values(n-1,2) = edge_values(n,1)
176
177 ! Loop on cells to bound edge value
178 do k = 1, n
179
180 ! For the sake of bounding boundary edge values, the left neighbor of the left boundary cell
181 ! is assumed to be the same as the left boundary cell and the right neighbor of the right
182 ! boundary cell is assumed to be the same as the right boundary cell. This effectively makes
183 ! boundary cells look like extrema.
184 km1 = max(1,k-1) ; kp1 = min(k+1,n)
185
186 slope_x_h = 0.0
187 sigma_l = ( u(k) - u(km1) )
188 if ( (h(km1) + h(kp1)) + 2.0*h(k) > 0. ) then
189 sigma_c = ( u(kp1) - u(km1) ) * ( h(k) / ((h(km1) + h(kp1)) + 2.0*h(k)) )
190 else
191 sigma_c = 0.
192 endif
193 sigma_r = ( u(kp1) - u(k) )
194
195 ! The limiter is used in the local coordinate system to each cell, so for convenience store
196 ! the slope times a half grid spacing. (See White and Adcroft JCP 2008 Eqs 19 and 20)
197 if ( (sigma_l * sigma_r) > 0.0 ) &
198 slope_x_h = sign( min(abs(sigma_l),abs(sigma_c),abs(sigma_r)), sigma_c )
199
200 ! Limit the edge values
201 if ( (u(km1)-edge_values(k,1)) * (edge_values(k,1)-u(k)) < 0.0 ) then
202 edge_values(k,1) = u(k) - sign( min( abs(slope_x_h), abs(edge_values(k,1)-u(k)) ), slope_x_h )
203 endif
204
205 if ( (u(kp1)-edge_values(k,2)) * (edge_values(k,2)-u(k)) < 0.0 ) then
206 edge_values(k,2) = u(k) + sign( min( abs(slope_x_h), abs(edge_values(k,2)-u(k)) ), slope_x_h )
207 endif
208
209 ! Finally bound by neighboring cell means in case of roundoff
210 edge_values(k,1) = max( min( edge_values(k,1), max(u(km1), u(k)) ), min(u(km1), u(k)) )
211 edge_values(k,2) = max( min( edge_values(k,2), max(u(kp1), u(k)) ), min(u(kp1), u(k)) )
212
213 enddo ! loop on interior edges
214
215 do k = 1, n-1
216 if ( (edge_values(k+1,1) - edge_values(k,2)) * (u(k+1) - u(k)) < 0.0 ) then
217 u0_avg = 0.5 * ( edge_values(k,2) + edge_values(k+1,1) )
218 u0_avg = max( min( u0_avg, max(u(k), u(k+1)) ), min(u(k), u(k+1)) )
219 edge_values(k,2) = u0_avg
220 edge_values(k+1,1) = u0_avg
221 endif
222 enddo ! end loop on interior edges
223
224 ! Loop on interior cells to apply the standard
225 ! PPM limiter (Colella & Woodward, JCP 84)
226 do k = 2,n-1
227
228 ! Get cell averages
229 u_l = u(k-1)
230 u_c = u(k)
231 u_r = u(k+1)
232
233 edge_l = edge_values(k,1)
234 edge_r = edge_values(k,2)
235
236 if ( (u_r - u_c)*(u_c - u_l) <= 0.0) then
237 ! Flatten extremum
238 edge_l = u_c
239 edge_r = u_c
240 else
241 expr1 = 3.0 * (edge_r - edge_l) * ( (u_c - edge_l) + (u_c - edge_r))
242 expr2 = (edge_r - edge_l) * (edge_r - edge_l)
243 if ( expr1 > expr2 ) then
244 ! Place extremum at right edge of cell by adjusting left edge value
245 edge_l = u_c + 2.0 * ( u_c - edge_r )
246 edge_l = max( min( edge_l, max(u_l, u_c) ), min(u_l, u_c) ) ! In case of round off
247 elseif ( expr1 < -expr2 ) then
248 ! Place extremum at left edge of cell by adjusting right edge value
249 edge_r = u_c + 2.0 * ( u_c - edge_l )
250 edge_r = max( min( edge_r, max(u_r, u_c) ), min(u_r, u_c) ) ! In case of round off
251 endif
252 endif
253 ! This checks that the difference in edge values is representable
254 ! and avoids overshoot problems due to round off.
255 !### The 1.e-60 needs to have units of [A], so this dimensionally inconsistent.
256 if ( abs( edge_r - edge_l )<max(1.e-60,epsilon(u_c)*abs(u_c)) ) then
257 edge_l = u_c
258 edge_r = u_c
259 endif
260
261 edge_values(k,1) = edge_l
262 edge_values(k,2) = edge_r
263
264 enddo ! end loop on interior cells
265
266 ! PCM within boundary cells
267 edge_values(1,:) = u(1)
268 edge_values(n,:) = u(n)
269
270 ! Store reconstruction
271 do k = 1, n
272 this%u_mean(k) = u(k)
273 this%ul(k) = edge_values(k,1)
274 this%ur(k) = edge_values(k,2)
275 enddo
276
277end subroutine reconstruct
278
279!> Determine a one-sided 4th order polynomial fit of u to the data points for the purposes of specifying
280!! edge values, as described in the appendix of White and Adcroft JCP 2008.
281subroutine end_value_h4(dz, u, Csys)
282 real, intent(in) :: dz(4) !< The thicknesses of 4 layers, starting at the edge [H].
283 !! The values of dz must be positive.
284 real, intent(in) :: u(4) !< The average properties of 4 layers, starting at the edge [A]
285 real, intent(out) :: Csys(4) !< The four coefficients of a 4th order polynomial fit
286 !! of u as a function of z [A H-(n-1)]
287
288 ! Local variables
289 real :: Wt(3,4) ! The weights of successive u differences in the 4 closed form expressions.
290 ! The units of Wt vary with the second index as [H-(n-1)].
291 real :: h1, h2, h3, h4 ! Copies of the layer thicknesses [H]
292 real :: h12, h23, h34 ! Sums of two successive thicknesses [H]
293 real :: h123, h234 ! Sums of three successive thicknesses [H]
294 real :: h1234 ! Sums of all four thicknesses [H]
295 ! real :: I_h1 ! The inverse of the a thickness [H-1]
296 real :: I_h12, I_h23, I_h34 ! The inverses of sums of two thicknesses [H-1]
297 real :: I_h123, I_h234 ! The inverse of the sum of three thicknesses [H-1]
298 real :: I_h1234 ! The inverse of the sum of all four thicknesses [H-1]
299 real :: I_denom ! The inverse of the denominator some expressions [H-3]
300 real :: I_denB3 ! The inverse of the product of three sums of thicknesses [H-3]
301 real :: min_frac = 1.0e-6 ! The square of min_frac should be much larger than roundoff [nondim]
302 real, parameter :: C1_3 = 1.0 / 3.0 ! A rational parameter [nondim]
303
304 ! if ((dz(1) == dz(2)) .and. (dz(1) == dz(3)) .and. (dz(1) == dz(4))) then
305 ! ! There are simple closed-form expressions in this case
306 ! I_h1 = 0.0 ; if (dz(1) > 0.0) I_h1 = 1.0 / dz(1)
307 ! Csys(1) = u(1) + (-13.0 * (u(2)-u(1)) + 10.0 * (u(3)-u(2)) - 3.0 * (u(4)-u(3))) * (0.25*C1_3)
308 ! Csys(2) = (35.0 * (u(2)-u(1)) - 34.0 * (u(3)-u(2)) + 11.0 * (u(4)-u(3))) * (0.25*C1_3 * I_h1)
309 ! Csys(3) = (-5.0 * (u(2)-u(1)) + 8.0 * (u(3)-u(2)) - 3.0 * (u(4)-u(3))) * (0.25 * I_h1**2)
310 ! Csys(4) = ((u(2)-u(1)) - 2.0 * (u(3)-u(2)) + (u(4)-u(3))) * (0.5*C1_3)
311 ! else
312
313 ! Express the coefficients as sums of the differences between properties of successive layers.
314
315 h1 = dz(1) ; h2 = dz(2) ; h3 = dz(3) ; h4 = dz(4)
316 ! Some of the weights used below are proportional to (h1/(h2+h3))**2 or (h1/(h2+h3))*(h2/(h3+h4))
317 ! so h2 and h3 should be adjusted to ensure that these ratios are not so large that property
318 ! differences at the level of roundoff are amplified to be of order 1.
319 if ((h2+h3) < min_frac*h1) h3 = min_frac*h1 - h2
320 if ((h3+h4) < min_frac*h1) h4 = min_frac*h1 - h3
321
322 h12 = h1+h2 ; h23 = h2+h3 ; h34 = h3+h4
323 h123 = h12 + h3 ; h234 = h2 + h34 ; h1234 = h12 + h34
324 ! Find 3 reciprocals with a single division for efficiency.
325 i_denb3 = 1.0 / (h123 * h12 * h23)
326 i_h12 = (h123 * h23) * i_denb3
327 i_h23 = (h12 * h123) * i_denb3
328 i_h123 = (h12 * h23) * i_denb3
329 i_denom = 1.0 / ( h1234 * (h234 * h34) )
330 i_h34 = (h1234 * h234) * i_denom
331 i_h234 = (h1234 * h34) * i_denom
332 i_h1234 = (h234 * h34) * i_denom
333
334 ! Calculation coefficients in the four equations
335
336 ! The expressions for Csys(3) and Csys(4) come from reducing the 4x4 matrix problem into the following 2x2
337 ! matrix problem, then manipulating the analytic solution to avoid any subtraction and simplifying.
338 ! (C1_3 * h123 * h23) * Csys(3) + (0.25 * h123 * h23 * (h3 + 2.0*h2 + 3.0*h1)) * Csys(4) =
339 ! (u(3)-u(1)) - (u(2)-u(1)) * (h12 + h23) * I_h12
340 ! (C1_3 * ((h23 + h34) * h1234 + h23 * h3)) * Csys(3) +
341 ! (0.25 * ((h1234 + h123 + h12 + h1) * h23 * h3 + (h1234 + h12 + h1) * (h23 + h34) * h1234)) * Csys(4) =
342 ! (u(4)-u(1)) - (u(2)-u(1)) * (h123 + h234) * I_h12
343 ! The final expressions for Csys(1) and Csys(2) were derived by algebraically manipulating the following expressions:
344 ! Csys(1) = (C1_3 * h1 * h12 * Csys(3) + 0.25 * h1 * h12 * (2.0*h1+h2) * Csys(4)) - &
345 ! (h1*I_h12)*(u(2)-u(1)) + u(1)
346 ! Csys(2) = (-2.0*C1_3 * (2.0*h1+h2) * Csys(3) - 0.5 * (h1**2 + h12 * (2.0*h1+h2)) * Csys(4)) + &
347 ! 2.0*I_h12 * (u(2)-u(1))
348 ! These expressions are typically evaluated at x=0 and x=h1, so it is important that these are well behaved
349 ! for these values, suggesting that h1/h23 and h1/h34 should not be allowed to be too large.
350
351 wt(1,1) = -h1 * (i_h1234 + i_h123 + i_h12) ! > -3
352 wt(2,1) = h1 * h12 * ( i_h234 * i_h1234 + i_h23 * (i_h234 + i_h123) ) ! < (h1/h234) + (h1/h23)*(2+(h1/h234))
353 wt(3,1) = -h1 * h12 * h123 * i_denom ! > -(h1/h34)*(1+(h1/h234))
354
355 wt(1,2) = 2.0 * (i_h12*(1.0 + (h1+h12) * (i_h1234 + i_h123)) + h1 * i_h1234*i_h123) ! < 10/h12
356 wt(2,2) = -2.0 * ((h1 * h12 * i_h1234) * (i_h23 * (i_h234 + i_h123)) + & ! > -(10+6*(h1/h234))/h23
357 (h1+h12) * ( i_h1234*i_h234 + i_h23 * (i_h234 + i_h123) ) )
358 wt(3,2) = 2.0 * ((h1+h12) * h123 + h1*h12 ) * i_denom ! < (2+(6*h1/h234)) / h34
359
360 wt(1,3) = -3.0 * i_h12 * i_h123* ( 1.0 + i_h1234 * ((h1+h12)+h123) ) ! > -12 / (h12*h123)
361 wt(2,3) = 3.0 * i_h23 * ( i_h123 + i_h1234 * ((h1+h12)+h123) * (i_h123 + i_h234) ) ! < 12 / (h23^2)
362 wt(3,3) = -3.0 * ((h1+h12)+h123) * i_denom ! > -9 / (h234*h23)
363
364 wt(1,4) = 4.0 * i_h1234 * i_h123 * i_h12 ! Wt*h1^3 < 4
365 wt(2,4) = -4.0 * i_h1234 * (i_h23 * (i_h123 + i_h234)) ! Wt*h1^3 > -4* (h1/h23)*(1+h1/h234)
366 wt(3,4) = 4.0 * i_denom ! = 4.0*I_h1234 * I_h234 * I_h34 ! Wt*h1^3 < 4 * (h1/h234)*(h1/h34)
367
368 csys(1) = ((u(1) + wt(1,1) * (u(2)-u(1))) + wt(2,1) * (u(3)-u(2))) + wt(3,1) * (u(4)-u(3))
369 csys(2) = (wt(1,2) * (u(2)-u(1)) + wt(2,2) * (u(3)-u(2))) + wt(3,2) * (u(4)-u(3))
370 csys(3) = (wt(1,3) * (u(2)-u(1)) + wt(2,3) * (u(3)-u(2))) + wt(3,3) * (u(4)-u(3))
371 csys(4) = (wt(1,4) * (u(2)-u(1)) + wt(2,4) * (u(3)-u(2))) + wt(3,4) * (u(4)-u(3))
372
373 ! endif ! End of non-uniform layer thickness branch.
374
375end subroutine end_value_h4
376
377!> Value of PPM_H4_2019 reconstruction at a point in cell k [A]
378real function f(this, k, x)
379 class(ppm_h4_2019), intent(in) :: this !< This reconstruction
380 integer, intent(in) :: k !< Cell number
381 real, intent(in) :: x !< Non-dimensional position within element [nondim]
382 real :: xc ! Bounded version of x [nondim]
383 real :: du ! Difference across cell [A]
384 real :: a6 ! Collela and Woordward curvature parameter [A]
385 real :: u_a, u_b ! Two estimate of f [A]
386 real :: lmx ! 1 - x [nondim]
387 real :: wb ! Weight based on x [nondim]
388
389 du = this%ur(k) - this%ul(k)
390 a6 = 3.0 * ( ( this%u_mean(k) - this%ul(k) ) + ( this%u_mean(k) - this%ur(k) ) )
391 xc = max( 0., min( 1., x ) )
392 lmx = 1.0 - xc
393
394 ! This expression for u_a can overshoot u_r but is good for x<<1
395 u_a = this%ul(k) + xc * ( du + a6 * lmx )
396 ! This expression for u_b can overshoot u_l but is good for 1-x<<1
397 u_b = this%ur(k) + lmx * ( - du + a6 * xc )
398
399 ! Since u_a and u_b are both side-bounded, using weights=0 or 1 will preserve uniformity
400 wb = 0.5 + sign(0.5, xc - 0.5 ) ! = 1 @ x=0, = 0 @ x=1
401 f = ( ( 1. - wb ) * u_a ) + ( wb * u_b )
402
403end function f
404
405!> Derivative of PPM_H4_2019 reconstruction at a point in cell k [A]
406real function dfdx(this, k, x)
407 class(ppm_h4_2019), intent(in) :: this !< This reconstruction
408 integer, intent(in) :: k !< Cell number
409 real, intent(in) :: x !< Non-dimensional position within element [nondim]
410 real :: xc ! Bounded version of x [nondim]
411 real :: du ! Difference across cell [A]
412 real :: a6 ! Collela and Woordward curvature parameter [A]
413
414 du = this%ur(k) - this%ul(k)
415 a6 = 3.0 * ( ( this%u_mean(k) - this%ul(k) ) + ( this%u_mean(k) - this%ur(k) ) )
416 xc = max( 0., min( 1., x ) )
417
418 dfdx = du + a6 * ( 2.0 * xc - 1.0 )
419
420end function dfdx
421
422!> Average between xa and xb for cell k of a 1D PPM reconstruction [A]
423real function average(this, k, xa, xb)
424 class(ppm_h4_2019), intent(in) :: this !< This reconstruction
425 integer, intent(in) :: k !< Cell number
426 real, intent(in) :: xa !< Start of averaging interval on element (0 to 1)
427 real, intent(in) :: xb !< End of averaging interval on element (0 to 1)
428 real :: xapxb ! A sum of fracional positions [nondim]
429 real :: mx, ya, yb, my ! Various fractional positions [nondim]
430 real :: u_a, u_b ! Values at xa and xb [A]
431 real :: xa2pxb2, xa2b2ab, ya2b2ab ! Sums of squared fractional positions [nondim]
432 real :: a_l, a_r, u_c, a_c ! Values of the polynomial at various locations [A]
433
434 mx = 0.5 * ( xa + xb )
435 a_l = this%ul(k)
436 a_r = this%ur(k)
437 u_c = this%u_mean(k)
438 a_c = 0.5 * ( ( u_c - a_l ) + ( u_c - a_r ) ) ! a_6 / 6
439 if (mx<0.5) then
440 ! This integration of the PPM reconstruction is expressed in distances from the left edge
441 xa2b2ab = (xa*xa+xb*xb)+xa*xb
442 average = a_l + ( ( a_r - a_l ) * mx &
443 + a_c * ( 3. * ( xb + xa ) - 2.*xa2b2ab ) )
444 else
445 ! This integration of the PPM reconstruction is expressed in distances from the right edge
446 ya = 1. - xa
447 yb = 1. - xb
448 my = 0.5 * ( ya + yb )
449 ya2b2ab = (ya*ya+yb*yb)+ya*yb
450 average = a_r + ( ( a_l - a_r ) * my &
451 + a_c * ( 3. * ( yb + ya ) - 2.*ya2b2ab ) )
452 endif
453
454end function average
455
456!> Deallocate the PPM_H4_2019 reconstruction
457subroutine destroy(this)
458 class(ppm_h4_2019), intent(inout) :: this !< This reconstruction
459
460 deallocate( this%u_mean, this%ul, this%ur )
461
462end subroutine destroy
463
464!> Checks the PPM_H4_2019 reconstruction for consistency
465logical function check_reconstruction(this, h, u)
466 class(ppm_h4_2019), intent(in) :: this !< This reconstruction
467 real, intent(in) :: h(*) !< Grid spacing (thickness) [typically H]
468 real, intent(in) :: u(*) !< Cell mean values [A]
469 ! Local variables
470 integer :: k
471
472 check_reconstruction = .false.
473
474 ! Simply checks the internal copy of "u" is exactly equal to "u"
475 do k = 1, this%n
476 if ( abs( this%u_mean(k) - u(k) ) > 0. ) check_reconstruction = .true.
477 enddo
478
479 ! If (u - ul) has the opposite sign from (ur - u), then this cell has an interior extremum
480 do k = 1, this%n
481 if ( ( this%u_mean(k) - this%ul(k) ) * ( this%ur(k) - this%u_mean(k) ) < 0. ) check_reconstruction = .true.
482 enddo
483
484 ! Check bounding of right edges, w.r.t. the cell means
485 do k = 1, this%n-1
486 if ( ( this%ur(k) - this%u_mean(k) ) * ( this%u_mean(k+1) - this%ur(k) ) < 0. ) check_reconstruction = .true.
487 enddo
488
489 ! Check bounding of left edges, w.r.t. the cell means
490 do k = 2, this%n
491 if ( ( this%u_mean(k) - this%ul(k) ) * ( this%ul(k) - this%u_mean(k-1) ) < 0. ) check_reconstruction = .true.
492 enddo
493
494 ! Check bounding of right edges, w.r.t. this cell mean and the next cell left edge
495 do k = 1, this%n-1
496 if ( ( this%ur(k) - this%u_mean(k) ) * ( this%ul(k+1) - this%ur(k) ) < 0. ) check_reconstruction = .true.
497 enddo
498
499 ! Check bounding of left edges, w.r.t. this cell mean and the previous cell right edge
500 do k = 2, this%n
501 if ( ( this%u_mean(k) - this%ul(k) ) * ( this%ul(k) - this%ur(k-1) ) < 0. ) check_reconstruction = .true.
502 enddo
503
504end function check_reconstruction
505
506!> Runs PPM_H4_2019 reconstruction unit tests and returns True for any fails, False otherwise
507logical function unit_tests(this, verbose, stdout, stderr)
508 class(ppm_h4_2019), intent(inout) :: this !< This reconstruction
509 logical, intent(in) :: verbose !< True, if verbose
510 integer, intent(in) :: stdout !< I/O channel for stdout
511 integer, intent(in) :: stderr !< I/O channel for stderr
512 ! Local variables
513 real, allocatable :: ul(:), ur(:), um(:) ! test values [A]
514 real, allocatable :: ull(:), urr(:) ! test values [A]
515 type(testing) :: test ! convenience functions
516 integer :: k
517
518 call test%set( stdout=stdout ) ! Sets the stdout channel in test
519 call test%set( stderr=stderr ) ! Sets the stderr channel in test
520 call test%set( verbose=verbose ) ! Sets the verbosity flag in test
521
522 if (verbose) write(stdout,'(a)') 'PPM_H4_2019:unit_tests testing with linear fn'
523
524 call this%init(5)
525 call test%test( this%n /= 5, 'Setting number of levels')
526 allocate( um(5), ul(5), ur(5), ull(5), urr(5) )
527
528 ! Straight line, f(x) = x , or f(K) = 2*K
529 call this%reconstruct( (/2.,2.,2.,2.,2./), (/1.,3.,5.,7.,9./) )
530 call test%real_arr(5, this%u_mean, (/1.,3.,5.,7.,9./), 'Setting cell values')
531 call test%real_arr(5, this%ul, (/1.,2.,4.,6.,9./), 'Left edge values', robits=2)
532 call test%real_arr(5, this%ur, (/1.,4.,6.,8.,9./), 'Right edge values')
533 do k = 1, 5
534 um(k) = this%u_mean(k)
535 enddo
536 call test%real_arr(5, um, (/1.,3.,5.,7.,9./), 'Return cell mean')
537
538 do k = 1, 5
539 ul(k) = this%f(k, 0.)
540 um(k) = this%f(k, 0.5)
541 ur(k) = this%f(k, 1.)
542 enddo
543 call test%real_arr(5, ul, this%ul, 'Evaluation on left edge')
544 call test%real_arr(5, um, (/1.,3.,5.,7.,9./), 'Evaluation in center')
545 call test%real_arr(5, ur, this%ur, 'Evaluation on right edge')
546
547 do k = 1, 5
548 ul(k) = this%dfdx(k, 0.)
549 um(k) = this%dfdx(k, 0.5)
550 ur(k) = this%dfdx(k, 1.)
551 enddo
552 call test%real_arr(5, ul, (/0.,2.,2.,2.,0./), 'dfdx on left edge', robits=3)
553 call test%real_arr(5, um, (/0.,2.,2.,2.,0./), 'dfdx in center', robits=2)
554 call test%real_arr(5, ur, (/0.,2.,2.,2.,0./), 'dfdx on right edge', robits=6)
555
556 do k = 1, 5
557 um(k) = this%average(k, 0.5, 0.75) ! Average from x=0.25 to 0.75 in each cell
558 enddo
559 call test%real_arr(5, um, (/1.,3.25,5.25,7.25,9./), 'Return interval average')
560
561 if (verbose) write(stdout,'(a)') 'PPM_H4_2019:unit_tests testing with parabola'
562
563 ! x = 3 i i=0 at origin
564 ! f(x) = x^2 / 3 = 3 i^2
565 ! f[i] = [ ( 3 i )^3 - ( 3 i - 3 )^3 ] i=1,2,3,4,5
566 ! means: 1, 7, 19, 37, 61
567 ! edges: 0, 3, 12, 27, 48, 75
568 call this%reconstruct( (/3.,3.,3.,3.,3./), (/1.,7.,19.,37.,61./) )
569 do k = 1, 5
570 ul(k) = this%f(k, 0.)
571 um(k) = this%f(k, 0.5)
572 ur(k) = this%f(k, 1.)
573 enddo
574 call test%real_arr(5, ul, (/1.,3.,12.,27.,61./), 'Return left edge', robits=2)
575 call test%real_arr(5, ur, (/1.,12.,27.,48.,61./), 'Return right edge', robits=1)
576
577 call this%destroy()
578 deallocate( um, ul, ur, ull, urr )
579
580 unit_tests = test%summarize('PPM_H4_2019:unit_tests')
581
582end function unit_tests
583
584!> \namespace recon1d_ppm_c4_2019
585!!
586
587end module recon1d_ppm_h4_2019