Recon1d_PPM_CW.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 following Colella and Woodward, 1984
6!!
7!! This is a near faithful implementation of PPM following Colella and Woodward, 1984, with
8!! cells resorting to PCM for extrema including first and last cells in column. The
9!! only exception is that the PLM slopes used for edge interpolation are not set to zero
10!! for the first and last cells, but are side-differenced. This improves accuracy of edge
11!! values near boundaries and reduces the adverse influence of the boundaries on the
12!! interior reconstructions. The final PPM reconstruction in the first and last cells are
13!! set to PCM. The reconstructions are grid-spacing dependent, and so quasi-forth order in h.
14module recon1d_ppm_cw
15
16use recon1d_type, only : recon1d, testing
17use recon1d_plm_cw, only : plm_cw
18
19implicit none ; private
20
21public ppm_cw, testing
22
23!> PPM reconstruction following Colella and Woordward, 1984.
24!!
25!! The source for the methods ultimately used by this class are:
26!! - init() *locally defined
27!! - reconstruct() *locally defined
28!! - average() *locally defined
29!! - f() *locally defined
30!! - dfdx() *locally defined
31!! - x() *locally defined
32!! - check_reconstruction() *locally defined
33!! - unit_tests() *locally defined
34!! - destroy() *locally defined
35!! - remap_to_sub_grid() -> recon1d_type.remap_to_sub_grid()
36!! - init_parent() -> init()
37!! - reconstruct_parent() -> reconstruct()
38type, extends (recon1d) :: ppm_cw
39
40 real, allocatable :: ul(:) !< Left edge value [A]
41 real, allocatable :: ur(:) !< Right edge value [A]
42 type(plm_cw) :: plm !< The PLM reconstruction used to estimate edge values
43
44contains
45 !> Implementation of the PPM_CW initialization
46 procedure :: init => init
47 !> Implementation of the PPM_CW reconstruction
48 procedure :: reconstruct => reconstruct
49 !> Implementation of the PPM_CW average over an interval [A]
50 procedure :: average => average
51 !> Implementation of evaluating the PPM_CW reconstruction at a point [A]
52 procedure :: f => f
53 !> Implementation of the derivative of the PPM_CW reconstruction at a point [A]
54 procedure :: dfdx => dfdx
55 !> Implementation of solver for x: f(x)=t
56! procedure :: x => x
57 !> Implementation of deallocation for PPM_CW
58 procedure :: destroy => destroy
59 !> Implementation of check reconstruction for the PPM_CW reconstruction
61 !> Implementation of unit tests for the PPM_CW reconstruction
62 procedure :: unit_tests => unit_tests
63
64 !> Duplicate interface to init()
65 procedure :: init_parent => init
66 !> Duplicate interface to reconstruct()
67 procedure :: reconstruct_parent => reconstruct
68
69end type ppm_cw
70
71contains
72
73!> Initialize a 1D PPM_CW reconstruction for n cells
74subroutine init(this, n, h_neglect, check)
75 class(ppm_cw), intent(out) :: this !< This reconstruction
76 integer, intent(in) :: n !< Number of cells in this column
77 real, optional, intent(in) :: h_neglect !< A negligibly small width used in cell reconstructions [H]
78 logical, optional, intent(in) :: check !< If true, enable some consistency checking
79
80 this%n = n
81
82 allocate( this%u_mean(n) )
83 allocate( this%ul(n) )
84 allocate( this%ur(n) )
85
86 ! This incurs an extra store of u_mean but by using PCM_CW
87 ! we avoid duplicating and testing more code
88 call this%PLM%init( n, h_neglect=h_neglect, check=check )
89
90 this%h_neglect = tiny( this%u_mean(1) )
91 if (present(h_neglect)) this%h_neglect = h_neglect
92 this%check = .false.
93 if (present(check)) this%check = check
94
95end subroutine init
96
97!> Calculate a 1D PPM_CW reconstructions based on h(:) and u(:)
98subroutine reconstruct(this, h, u)
99 class(ppm_cw), intent(inout) :: this !< This reconstruction
100 real, intent(in) :: h(*) !< Grid spacing (thickness) [typically H]
101 real, intent(in) :: u(*) !< Cell mean values [A]
102 ! Local variables
103 real :: h0, h1, h2, h3 ! Cell thickness h(k-2), h(k-1), h(k), h(k+1) in K loop [H]
104 real :: d12 ! h1 + h2 but used in the denominator so include h_neglect [H]
105 real :: h01_h112, h23_h122 ! Approximately 2/3 [nondim]
106 real :: ddh ! Approximately 0 [nondim]
107 real :: I_h12, I_h0123 ! Reciprocals of d12 and sum(h) [H-1]
108 real :: dul, dur ! Left and right cell PLM slopes [A]
109 real :: u0, u1, u2 ! Far left, left, and right cell values [A]
110 real :: edge ! Edge value between cell k-1 and k [A]
111 real :: u_min, u_max ! Minimum and maximum value across edge [A]
112 real :: a6 ! Colella and Woodward curvature [A]
113 real :: du ! Difference between edges across cell [A]
114 real :: slp(this%n) ! PLM slope [A]
115 integer :: k, n
116
117 n = this%n
118
119 ! First populate the PLM reconstructions
120 call this%PLM%reconstruct( h, u )
121 do k = 1, n
122 slp(k) = this%PLM%ur(k) - this%PLM%ul(k)
123 enddo
124 ! Extrapolate from interior for boundary PLM slopes
125 ! Note: this is not conventional but helps retain accuracy near top/bottom
126 ! boundaries and reduces the adverse influence of the boundaries in the interior
127 ! reconstructions. The final PPM reconstruction is still bounded to PCM.
128 slp(1) = 2.0 * ( this%PLM%ul(2) - u(1) )
129 slp(n) = 2.0 * ( u(n) - this%PLM%ur(n-1) )
130
131 do k = 2, n ! K=2 is interface between cells 1 and 2
132 h0 = h( max( 1, k-2 ) ) ! This treatment implies a virtual mirror cell at k=0
133 h1 = h(k-1)
134 h2 = h(k)
135 h3 = h( min( n, k+1 ) ) ! This treatment implies a virtual mirror cell at k=n+1
136 d12 = ( h1 + h2 ) + this%h_neglect ! d12 is only ever used in the denominator
137 h01_h112 = ( h0 + h1 ) / ( h1 + d12 ) ! When uniform -> 2/3
138 h23_h122 = ( h2 + h3 ) / ( d12 + h2 ) ! When uniform -> 2/3
139 ddh = h01_h112 - h23_h122 ! When uniform -> 0
140 i_h12 = 1.0 / d12 ! When uniform -> 1/(2h)
141 i_h0123 = 1.0 / ( d12 + ( h0 + h3 ) ) ! When uniform -> 1/(4h)
142 dul = slp(k-1)
143 dur = slp(k)
144 u2 = u(k)
145 u1 = u(k-1)
146 edge = i_h12 * ( h2 * u1 + h1 * u2 ) & ! 1/2 u1 + 1/2 u2
147 + i_h0123 * ( 2.0 * h1 * h2 * i_h12 * ddh * ( u2 - u1 ) & ! 0
148 + ( h2 * h23_h122 * dul - h1 * h01_h112 * dur ) ) ! 1/6 dul - 1/6 dur
149 u_min = min( u1, u2 )
150 u_max = max( u1, u2 )
151 edge = max( min( edge, u_max), u_min ) ! Unclear if we need this bounding in the interior
152 this%ur(k-1) = edge
153 this%ul(k) = edge
154 enddo
155 this%ul(1) = u(1) ! PCM
156 this%ur(1) = u(1) ! PCM
157 this%ur(n) = u(n) ! PCM
158 this%ul(n) = u(n) ! PCM
159
160 do k = 2, n-1 ! K=2 is interface between cells 1 and 2
161 u0 = u(k-1)
162 u1 = u(k)
163 u2 = u(k+1)
164 a6 = 3.0 * ( ( u1 - this%ul(k) ) + ( u1 - this%ur(k) ) )
165 du = this%ur(k) - this%ul(k)
166 if ( ( u2 - u1 ) * ( u1 - u0 ) <= 0.0 ) then ! Large scale extrema
167 this%ul(k) = u1
168 this%ur(k) = u1
169 elseif ( du * a6 > du * du ) then ! Extrema on right
170 ! edge = 3.0 * u1 - 2.0 * this%ur(k) ! OM4 era expressions is subject to round off
171 edge = u1 + 2.0 * ( u1 - this%ur(k) ) ! Passes consistency tests - AJA
172 ! The following bounds were applied in OM4 era schemes but are not needed now
173 ! u_min = min( u0, u1 )
174 ! u_max = max( u0, u1 )
175 ! edge = max( min( edge, u_max), u_min )
176 this%ul(k) = edge
177 elseif ( du * a6 < - du * du ) then ! Extrema on left
178 ! edge = 3.0 * u1 - 2.0 * this%ul(k) ! OM4 era expressions is subject to round off
179 edge = u1 + 2.0 * ( u1 - this%ul(k) ) ! Passes consistency tests - AJA
180 ! The following bounds were applied in OM4 era schemes but are not needed now
181 ! u_min = min( u1, u2 )
182 ! u_max = max( u1, u2 )
183 ! edge = max( min( edge, u_max), u_min )
184 this%ur(k) = edge
185 endif
186 enddo
187
188 ! After the limiter, are ur and ul bounded???? -AJA
189
190 ! Store mean
191 do k = 1, n
192 this%u_mean(k) = u(k)
193 enddo
194
195end subroutine reconstruct
196
197!> Value of PPM_CW reconstruction at a point in cell k [A]
198real function f(this, k, x)
199 class(ppm_cw), intent(in) :: this !< This reconstruction
200 integer, intent(in) :: k !< Cell number
201 real, intent(in) :: x !< Non-dimensional position within element [nondim]
202 real :: xc ! Bounded version of x [nondim]
203 real :: du ! Difference across cell [A]
204 real :: a6 ! Collela and Woordward curvature parameter [A]
205 real :: u_a, u_b ! Two estimate of f [A]
206 real :: lmx ! 1 - x [nondim]
207 real :: wb ! Weight based on x [nondim]
208
209 du = this%ur(k) - this%ul(k)
210 a6 = 3.0 * ( ( this%u_mean(k) - this%ul(k) ) + ( this%u_mean(k) - this%ur(k) ) )
211 xc = max( 0., min( 1., x ) )
212 lmx = 1.0 - xc
213
214 ! This expression for u_a can overshoot u_r but is good for x<<1
215 u_a = this%ul(k) + xc * ( du + a6 * lmx )
216 ! This expression for u_b can overshoot u_l but is good for 1-x<<1
217 u_b = this%ur(k) + lmx * ( - du + a6 * xc )
218
219 ! Since u_a and u_b are both side-bounded, using weights=0 or 1 will preserve uniformity
220 wb = 0.5 + sign(0.5, xc - 0.5 ) ! = 1 @ x=0, = 0 @ x=1
221 f = ( ( 1. - wb ) * u_a ) + ( wb * u_b )
222
223end function f
224
225!> Derivative of PPM_CW reconstruction at a point in cell k [A]
226real function dfdx(this, k, x)
227 class(ppm_cw), intent(in) :: this !< This reconstruction
228 integer, intent(in) :: k !< Cell number
229 real, intent(in) :: x !< Non-dimensional position within element [nondim]
230 real :: xc ! Bounded version of x [nondim]
231 real :: du ! Difference across cell [A]
232 real :: a6 ! Collela and Woordward curvature parameter [A]
233
234 du = this%ur(k) - this%ul(k)
235 a6 = 3.0 * ( ( this%u_mean(k) - this%ul(k) ) + ( this%u_mean(k) - this%ur(k) ) )
236 xc = max( 0., min( 1., x ) )
237
238 dfdx = du + a6 * ( 2.0 * xc - 1.0 )
239
240end function dfdx
241
242!> Average between xa and xb for cell k of a 1D PPM reconstruction [A]
243real function average(this, k, xa, xb)
244 class(ppm_cw), intent(in) :: this !< This reconstruction
245 integer, intent(in) :: k !< Cell number
246 real, intent(in) :: xa !< Start of averaging interval on element (0 to 1)
247 real, intent(in) :: xb !< End of averaging interval on element (0 to 1)
248 real :: xapxb ! A sum of fracional positions [nondim]
249 real :: mx, ya, yb, my ! Various fractional positions [nondim]
250 real :: u_a, u_b ! Values at xa and xb [A]
251 real :: xa2pxb2, xa2b2ab, ya2b2ab ! Sums of squared fractional positions [nondim]
252 real :: a_l, a_r, u_c, a_c ! Values of the polynomial at various locations [A]
253
254 mx = 0.5 * ( xa + xb )
255 a_l = this%ul(k)
256 a_r = this%ur(k)
257 u_c = this%u_mean(k)
258 a_c = 0.5 * ( ( u_c - a_l ) + ( u_c - a_r ) ) ! a_6 / 6
259 if (mx<0.5) then
260 ! This integration of the PPM reconstruction is expressed in distances from the left edge
261 xa2b2ab = (xa * xa + xb * xb) + xa * xb
262 average = a_l + ( ( a_r - a_l ) * mx &
263 + a_c * ( 3. * ( xb + xa ) - 2. * xa2b2ab ) )
264 else
265 ! This integration of the PPM reconstruction is expressed in distances from the right edge
266 ya = 1. - xa
267 yb = 1. - xb
268 my = 0.5 * ( ya + yb )
269 ya2b2ab = (ya * ya + yb * yb) + ya * yb
270 average = a_r + ( ( a_l - a_r ) * my &
271 + a_c * ( 3. * ( yb + ya ) - 2. * ya2b2ab ) )
272 endif
273
274end function average
275
276!> Deallocate the PPM_CW reconstruction
277subroutine destroy(this)
278 class(ppm_cw), intent(inout) :: this !< This reconstruction
279
280 deallocate( this%u_mean, this%ul, this%ur )
281
282end subroutine destroy
283
284!> Checks the PPM_CW reconstruction for consistency
285logical function check_reconstruction(this, h, u)
286 class(ppm_cw), intent(in) :: this !< This reconstruction
287 real, intent(in) :: h(*) !< Grid spacing (thickness) [typically H]
288 real, intent(in) :: u(*) !< Cell mean values [A]
289 ! Local variables
290 integer :: k
291
292 check_reconstruction = .false.
293
294 ! Simply checks the internal copy of "u" is exactly equal to "u"
295 do k = 1, this%n
296 if ( abs( this%u_mean(k) - u(k) ) > 0. ) check_reconstruction = .true.
297 enddo
298
299 ! If (u - ul) has the opposite sign from (ur - u), then this cell has an interior extremum
300 do k = 1, this%n
301 if ( ( this%u_mean(k) - this%ul(k) ) * ( this%ur(k) - this%u_mean(k) ) < 0. ) check_reconstruction = .true.
302 enddo
303
304 ! Check bounding of right edges, w.r.t. the cell means
305 do k = 1, this%n-1
306 if ( ( this%ur(k) - this%u_mean(k) ) * ( this%u_mean(k+1) - this%ur(k) ) < 0. ) check_reconstruction = .true.
307 enddo
308
309 ! Check bounding of left edges, w.r.t. the cell means
310 do k = 2, this%n
311 if ( ( this%u_mean(k) - this%ul(k) ) * ( this%ul(k) - this%u_mean(k-1) ) < 0. ) check_reconstruction = .true.
312 enddo
313
314 ! Check bounding of right edges, w.r.t. this cell mean and the next cell left edge
315 do k = 1, this%n-1
316 if ( ( this%ur(k) - this%u_mean(k) ) * ( this%ul(k+1) - this%ur(k) ) < 0. ) check_reconstruction = .true.
317 enddo
318
319 ! Check bounding of left edges, w.r.t. this cell mean and the previous cell right edge
320 do k = 2, this%n
321 if ( ( this%u_mean(k) - this%ul(k) ) * ( this%ul(k) - this%ur(k-1) ) < 0. ) check_reconstruction = .true.
322 enddo
323
324end function check_reconstruction
325
326!> Runs PPM_CW reconstruction unit tests and returns True for any fails, False otherwise
327logical function unit_tests(this, verbose, stdout, stderr)
328 class(ppm_cw), intent(inout) :: this !< This reconstruction
329 logical, intent(in) :: verbose !< True, if verbose
330 integer, intent(in) :: stdout !< I/O channel for stdout
331 integer, intent(in) :: stderr !< I/O channel for stderr
332 ! Local variables
333 real, allocatable :: ul(:), ur(:), um(:) ! test values [A]
334 real, allocatable :: ull(:), urr(:) ! test values [A]
335 type(testing) :: test ! convenience functions
336 integer :: k
337
338 call test%set( stdout=stdout ) ! Sets the stdout channel in test
339 call test%set( stderr=stderr ) ! Sets the stderr channel in test
340 call test%set( verbose=verbose ) ! Sets the verbosity flag in test
341call test%set( stop_instantly=.true. )
342
343 if (verbose) write(stdout,'(a)') 'PPM_CW:unit_tests testing with linear fn'
344
345 call this%init(5)
346 call test%test( this%n /= 5, 'Setting number of levels')
347 allocate( um(5), ul(5), ur(5), ull(5), urr(5) )
348
349 ! Straight line, f(x) = x , or f(K) = 2*K
350 call this%reconstruct( (/2.,2.,2.,2.,2./), (/1.,4.,7.,10.,13./) )
351 call test%real_arr(5, this%u_mean, (/1.,4.,7.,10.,13./), 'Setting cell values')
352 ! Without PLM extrapolation we get l(2)=2 and r(4)=12 due to PLM=0 in boundary cells. -AJA
353 call test%real_arr(5, this%ul, (/1.,2.5,5.5,8.5,13./), 'Left edge values')
354 call test%real_arr(5, this%ur, (/1.,5.5,8.5,11.5,13./), 'Right edge values')
355
356 do k = 1, 5
357 ul(k) = this%f(k, 0.)
358 um(k) = this%f(k, 0.5)
359 ur(k) = this%f(k, 1.)
360 enddo
361 call test%real_arr(5, ul, this%ul, 'Evaluation on left edge')
362 call test%real_arr(5, um, (/1.,4.,7.,10.,13./), 'Evaluation in center')
363 call test%real_arr(5, ur, this%ur, 'Evaluation on right edge')
364
365 do k = 1, 5
366 ul(k) = this%dfdx(k, 0.)
367 um(k) = this%dfdx(k, 0.5)
368 ur(k) = this%dfdx(k, 1.)
369 enddo
370 ! Most of these values are affected by the PLM boundary cells
371 call test%real_arr(5, ul, (/0.,3.,3.,3.,0./), 'dfdx on left edge')
372 call test%real_arr(5, um, (/0.,3.,3.,3.,0./), 'dfdx in center')
373 call test%real_arr(5, ur, (/0.,3.,3.,3.,0./), 'dfdx on right edge')
374
375 call test%real_scalar( this%x(2,1.), 0., 'f-1(2,1)=0')
376 call test%real_scalar( this%x(2,4.), 0.5, 'f-1(2,4)=0.5')
377 call test%real_scalar( this%x(2,5.5), 1., 'f-1(2,5.5)=1')
378
379 do k = 1, 5
380 um(k) = this%average(k, 0.5, 0.75) ! Average from x=0.25 to 0.75 in each cell
381 enddo
382 ! Most of these values are affected by the PLM boundary cells
383 call test%real_arr(5, um, (/1.,4.375,7.375,10.375,13./), 'Return interval average')
384
385 if (verbose) write(stdout,'(a)') 'PPM_CW:unit_tests testing with parabola'
386
387 ! x = 2 i i=0 at origin
388 ! f(x) = 3/4 x^2 = (2 i)^2
389 ! f[i] = 3/4 ( 2 i - 1 )^2 on centers
390 ! f[I] = 3/4 ( 2 I )^2 on edges
391 ! f[i] = 1/8 [ x^3 ] for means
392 ! edges: 0, 1, 12, 27, 48, 75
393 ! means: 1, 7, 19, 37, 61
394 ! centers: 0.75, 6.75, 18.75, 36.75, 60.75
395 call this%reconstruct( (/2.,2.,2.,2.,2./), (/1.,7.,19.,37.,61./) )
396 do k = 1, 5
397 ul(k) = this%f(k, 0.)
398 um(k) = this%f(k, 0.5)
399 ur(k) = this%f(k, 1.)
400 enddo
401 call test%real_arr(5, ul, (/1.,3.,12.,27.,61./), 'Return left edge')
402 call test%real_arr(5, um, (/1.,6.75,18.75,36.75,61./), 'Return center')
403 call test%real_arr(5, ur, (/1.,12.,27.,48.,61./), 'Return right edge')
404
405 call test%real_scalar( this%x(3,12.), 0., 'f-1(3,12)=0')
406 call test%real_scalar( this%x(3,18.75), 0.5, 'f-1(3,18.75)=0.5', robits=1)
407 call test%real_scalar( this%x(3,27.), 1., 'f-1(3,27)=1')
408
409 ! x = 3 i i=0 at origin
410 ! f(x) = x^2 / 3 = 3 i^2
411 ! f[i] = [ ( 3 i )^3 - ( 3 i - 3 )^3 ] i=1,2,3,4,5
412 ! means: 1, 7, 19, 37, 61
413 ! edges: 0, 3, 12, 27, 48, 75
414 call this%reconstruct( (/3.,3.,3.,3.,3./), (/1.,7.,19.,37.,61./) )
415 do k = 1, 5
416 ul(k) = this%f(k, 0.)
417 um(k) = this%f(k, 0.5)
418 ur(k) = this%f(k, 1.)
419 enddo
420 call test%real_arr(5, ul, (/1.,3.,12.,27.,61./), 'Return left edge')
421 call test%real_arr(5, um, (/1.,0.25*(6*7-15),0.25*(6*19-39),0.25*(6*37-75),61./), 'Return center')
422 call test%real_arr(5, ur, (/1.,12.,27.,48.,61./), 'Return right edge')
423
424 call this%destroy()
425 deallocate( um, ul, ur, ull, urr )
426
427 unit_tests = test%summarize('PPM_CW:unit_tests')
428
429end function unit_tests
430
431!> \namespace recon1d_ppm_cw
432!!
433
434end module recon1d_ppm_cw