Recon1d_PPM_CWK.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 in model index space
6!!
7!! This implementation of PPM follows Colella and Woodward, 1984, using uniform thickness
8!! and with cells resorting to PCM for local extrema including the first and last cells.
9!!
10!! "Fourth order" estimates of edge values use PLM also calculated in index space
11!! (i.e. with no grid dependence). First and last PLM slopes are extrapolated.
12!! Limiting follows Colella and Woodward thereafter. The high accuracy of this scheme is
13!! realized only when the grid-spacing is exactly uniform. This scheme deviates from CW84
14!! when the grid spacing is variable.
15module recon1d_ppm_cwk
16
17use recon1d_type, only : recon1d, testing
18use recon1d_plm_cwk, only : plm_cwk
19
20implicit none ; private
21
22public ppm_cwk, testing
23
24!> PPM reconstruction in index space (no grid dependence).
25!!
26!! The source for the methods ultimately used by this class are:
27!! - init() *locally defined
28!! - reconstruct() *locally defined
29!! - average() *locally defined
30!! - f() *locally defined
31!! - dfdx() *locally defined
32!! - x() *locally defined
33!! - check_reconstruction() *locally defined
34!! - unit_tests() *locally defined
35!! - destroy() *locally defined
36!! - remap_to_sub_grid() -> recon1d_type.remap_to_sub_grid()
37!! - init_parent() -> init()
38!! - reconstruct_parent() -> reconstruct()
40
41 real, allocatable :: ul(:) !< Left edge value [A]
42 real, allocatable :: ur(:) !< Right edge value [A]
43 type(plm_cwk) :: plm !< The PLM reconstruction used to estimate edge values
44
45contains
46 !> Implementation of the PPM_CWK initialization
48 !> Implementation of the PPM_CWK reconstruction
49 procedure :: reconstruct => reconstruct
50 !> Implementation of the PPM_CWK average over an interval [A]
52 !> Implementation of evaluating the PPM_CWK reconstruction at a point [A]
54 !> Implementation of the derivative of the PPM_CWK reconstruction at a point [A]
56 !> Implementation of solver for x: f(x)=t
58 !> Implementation of deallocation for PPM_CWK
60 !> Implementation of check reconstruction for the PPM_CWK reconstruction
61 procedure :: check_reconstruction => check_reconstruction
62 !> Implementation of unit tests for the PPM_CWK reconstruction
63 procedure :: unit_tests => unit_tests
64
65 !> Duplicate interface to init()
66 procedure :: init_parent => init
67 !> Duplicate interface to reconstruct()
68 procedure :: reconstruct_parent => reconstruct
69
70end type ppm_cwk
71
72contains
73
74!> Initialize a 1D PPM_CWK reconstruction for n cells
75subroutine init(this, n, h_neglect, check)
76 class(ppm_cwk), intent(out) :: this !< This reconstruction
77 integer, intent(in) :: n !< Number of cells in this column
78 real, optional, intent(in) :: h_neglect !< A negligibly small width used in cell reconstructions [H]
79 logical, optional, intent(in) :: check !< If true, enable some consistency checking
80
81 this%n = n
82
83 allocate( this%u_mean(n) )
84 allocate( this%ul(n) )
85 allocate( this%ur(n) )
86
87 ! This incurs an extra store of u_mean but by using PCM_CW
88 ! we avoid duplicating and testing more code
89 call this%PLM%init( n, h_neglect=h_neglect, check=check )
90
91 this%h_neglect = tiny( this%u_mean(1) )
92 if (present(h_neglect)) this%h_neglect = h_neglect
93 this%check = .false.
94 if (present(check)) this%check = check
95
96end subroutine init
97
98!> Calculate a 1D PPM_CWK reconstructions based on h(:) and u(:)
99subroutine reconstruct(this, h, u)
100 class(ppm_cwk), intent(inout) :: this !< This reconstruction
101 real, intent(in) :: h(*) !< Grid spacing (thickness) [typically H]
102 real, intent(in) :: u(*) !< Cell mean values [A]
103 ! Local variables
104 real :: dul, dur ! Left and right cell PLM slopes [A]
105 real :: u0, u1, u2 ! Far left, left, and right cell values [A]
106 real :: edge ! Edge value between cell k-1 and k [A]
107 real :: u_min, u_max ! Minimum and maximum value across edge [A]
108 real :: a6 ! Colella and Woodward curvature [A]
109 real :: du ! Difference between edges across cell [A]
110 real :: slp(this%n) ! PLM slope [A]
111 real, parameter :: one_sixth = 1. / 6. ! 1/6 [nondim]
112 integer :: k, n
113
114 n = this%n
115
116 ! First populate the PLM (k-space) reconstructions
117 call this%PLM%reconstruct( h, u )
118 do k = 1, n
119 slp(k) = this%PLM%ur(k) - this%PLM%ul(k)
120 enddo
121 ! Extrapolate from interior for boundary PLM slopes
122 ! Note: this is not conventional but helps retain accuracy near top/bottom
123 ! boudaries and reduces the adverse influence of the boudnaries int he interior
124 ! reconstructions. The final PPM reconstruction is still bounded to PCM.
125 slp(1) = 2.0 * ( this%PLM%ul(2) - u(1) )
126 slp(n) = 2.0 * ( u(n) - this%PLM%ur(n-1) )
127
128 do k = 2, n ! K=2 is interface between cells 1 and 2
129 dul = slp(k-1)
130 dur = slp(k)
131 u2 = u(k)
132 u1 = u(k-1)
133 edge = 0.5 * ( u1 + u2 ) + one_sixth * ( dul - dur ) ! Eq. 1.6 with uniform h
134 u_min = min( u1, u2 )
135 u_max = max( u1, u2 )
136 edge = max( min( edge, u_max), u_min ) ! Unclear if we need this bounding in the interior
137 this%ur(k-1) = edge
138 this%ul(k) = edge
139 enddo
140 this%ul(1) = u(1) ! PCM
141 this%ur(1) = u(1) ! PCM
142 this%ur(n) = u(n) ! PCM
143 this%ul(n) = u(n) ! PCM
144
145 do k = 2, n-1 ! K=2 is interface between cells 1 and 2
146 u0 = u(k-1)
147 u1 = u(k)
148 u2 = u(k+1)
149 a6 = 3.0 * ( ( u1 - this%ul(k) ) + ( u1 - this%ur(k) ) )
150 du = this%ur(k) - this%ul(k)
151 if ( ( u2 - u1 ) * ( u1 - u0 ) <= 0.0 ) then ! Large scale extrema
152 this%ul(k) = u1
153 this%ur(k) = u1
154 elseif ( du * a6 > du * du ) then ! Extrema on right
155 edge = u1 + 2.0 * ( u1 - this%ur(k) )
156 ! u_min = min( u0, u1 )
157 ! u_max = max( u0, u1 )
158 ! edge = max( min( edge, u_max), u_min )
159 this%ul(k) = edge
160 elseif ( du * a6 < - du * du ) then ! Extrema on left
161 edge = u1 + 2.0 * ( u1 - this%ul(k) )
162 ! u_min = min( u1, u2 )
163 ! u_max = max( u1, u2 )
164 ! edge = max( min( edge, u_max), u_min )
165 this%ur(k) = edge
166 endif
167 enddo
168
169 ! After the limiter, are ur and ul bounded???? -AJA
170
171 ! Store mean
172 do k = 1, n
173 this%u_mean(k) = u(k)
174 enddo
175
176end subroutine reconstruct
177
178!> Value of PPM_CWK reconstruction at a point in cell k [A]
179real function f(this, k, x)
180 class(ppm_cwk), intent(in) :: this !< This reconstruction
181 integer, intent(in) :: k !< Cell number
182 real, intent(in) :: x !< Non-dimensional position within element [nondim]
183 real :: xc ! Bounded version of x [nondim]
184 real :: du ! Difference across cell [A]
185 real :: a6 ! Collela and Woordward curvature parameter [A]
186 real :: u_a, u_b ! Two estimate of f [A]
187 real :: lmx ! 1 - x [nondim]
188 real :: wb ! Weight based on x [nondim]
189
190 du = this%ur(k) - this%ul(k)
191 a6 = 3.0 * ( ( this%u_mean(k) - this%ul(k) ) + ( this%u_mean(k) - this%ur(k) ) )
192 xc = max( 0., min( 1., x ) )
193 lmx = 1.0 - xc
194
195 ! This expression for u_a can overshoot u_r but is good for x<<1
196 u_a = this%ul(k) + xc * ( du + a6 * lmx )
197 ! This expression for u_b can overshoot u_l but is good for 1-x<<1
198 u_b = this%ur(k) + lmx * ( - du + a6 * xc )
199
200 ! Since u_a and u_b are both side-bounded, using weights=0 or 1 will preserve uniformity
201 wb = 0.5 + sign(0.5, xc - 0.5 ) ! = 1 @ x=0, = 0 @ x=1
202 f = ( ( 1. - wb ) * u_a ) + ( wb * u_b )
203
204end function f
205
206!> Derivative of PPM_CWK reconstruction at a point in cell k [A]
207real function dfdx(this, k, x)
208 class(ppm_cwk), intent(in) :: this !< This reconstruction
209 integer, intent(in) :: k !< Cell number
210 real, intent(in) :: x !< Non-dimensional position within element [nondim]
211 real :: xc ! Bounded version of x [nondim]
212 real :: du ! Difference across cell [A]
213 real :: a6 ! Collela and Woordward curvature parameter [A]
214
215 du = this%ur(k) - this%ul(k)
216 a6 = 3.0 * ( ( this%u_mean(k) - this%ul(k) ) + ( this%u_mean(k) - this%ur(k) ) )
217 xc = max( 0., min( 1., x ) )
218
219 dfdx = du + a6 * ( 2.0 * xc - 1.0 )
220
221end function dfdx
222
223!> Solver for x: f(x)=t
224real function x(this, k, t)
225 class(ppm_cwk), intent(in) :: this !< This reconstruction
226 integer, intent(in) :: k !< Cell number
227 real, intent(in) :: t !< Value to solve for [A]
228 real :: slp ! Difference in edge values, ur-ul [A]
229 real :: a6 ! Colella and Woodward curvature parameter [A]
230 real :: sd ! Square root of the quadratic discriminant [A]
231 real :: b ! The b in f(x) = a x^2 + b x + c [A]
232 real :: c ! The c in f(x) = a x^2 + b x + c [A]
233
234 ! The PPM profile is the quadratic profile: f(x) = ul + (slp+a6)*x - a6*x^2.
235 ! Setting f(x)=t gives: -a6*x^2 + (slp+a6)*x + (ul-t) = 0.
236 ! In the common parlance of solving a*x^2 + b*x + c = 0, this means
237 ! a = -a6; b = slp+a6; c = ul-t
238 ! The quadratic formula x = ( -b +/- sD ) / ( 2a ) with sD = sqrt(b^2-4*a*c)
239 ! can suffer from catastrophic cancellation in some scenarios.
240 ! A mathematically equivalent form of x = 2c / ( -b -/+ sD ) also can fail.
241 ! Usually, to avoid catastrophic cancellation, we use the rule
242 ! If b>0 then the two roots are
243 ! ra = -(b+sD)/(2a)
244 ! rc = -2c/(b+sD)
245 ! otherwise if b<0 then the two roots are
246 ! ra = (-b+sD)/(2a)
247 ! rc = 2c/(-b+sD)
248 ! In all expressions, sD and b do not have cancelling contributions due to the signs.
249 ! Note that here, if b>0 then c<0, and vice versa, because we are looking
250 ! for f(x)=t which shifts "c" by t so that the root we are interested in
251 ! falls in the range 0 <= x <= 1 (assuming t falls in ul...ur).
252 ! When b>0 and a>0 then -b/(2a)<0 and ra<0<rc, so we need rc
253 ! When b>0 and a<0 then -b/(2a)>0 and ra>rc, so we need rc
254 ! When b<0 and a>0 then -b/(2a)>0 and ra>rc, so we need rc
255 ! When b<0 and a<0 then -b/(2a)<0 and ra<0<rc, so we need rc
256 ! So a form that always gives us the root that we want is
257 ! x = -2c/(b+sgn(b)*sD)
258 slp = this%ur(k) - this%ul(k)
259 a6 = 3.0 * ((this%u_mean(k) - this%ul(k)) + (this%u_mean(k) - this%ur(k)))
260 b = slp + a6 ! to avoid repeated computation
261 c = this%ul(k) - t ! to avoid repeated computation
262 if (abs(slp) > 0.) then
263 ! The max(0,..a.) here is out of an abundance of caution, but if the PPM parameters
264 ! have been made monotonic then the max is not necessary.
265 sd = sqrt( max( 0., b**2 + 4. * a6 * c ) )
266 ! Calculate the reciprocal of the denominator. Note: even if b=0, sign(sD,b)=sD>0.
267 x = 1. / ( b + sign( sd, b ) )
268 ! The actual root is
271 else
272 ! Constant (or inconsistent) profile (ul=ur, a6=?): infer position from adjacent cell slopes.
273 x = 0.5 ! fallback
274 slp = this%ul(min(k+1,this%n)) - this%ur(max(k-1,1))
275 if (abs(slp) > 0.) x = 0.5 + sign( 0.5, slp ) ! either 0 or 1
276 endif
277end function x
278
279!> Average between xa and xb for cell k of a 1D PPM reconstruction [A]
280real function average(this, k, xa, xb)
281 class(ppm_cwk), intent(in) :: this !< This reconstruction
282 integer, intent(in) :: k !< Cell number
283 real, intent(in) :: xa !< Start of averaging interval on element (0 to 1)
284 real, intent(in) :: xb !< End of averaging interval on element (0 to 1)
285 real :: xapxb ! A sum of fracional positions [nondim]
286 real :: mx, ya, yb, my ! Various fractional positions [nondim]
287 real :: u_a, u_b ! Values at xa and xb [A]
288 real :: xa2pxb2, xa2b2ab, ya2b2ab ! Sums of squared fractional positions [nondim]
289 real :: a_l, a_r, u_c, a_c ! Values of the polynomial at various locations [A]
290
291 mx = 0.5 * ( xa + xb )
292 a_l = this%ul(k)
293 a_r = this%ur(k)
294 u_c = this%u_mean(k)
295 a_c = 0.5 * ( ( u_c - a_l ) + ( u_c - a_r ) ) ! a_6 / 6
296 if (mx<0.5) then
297 ! This integration of the PPM reconstruction is expressed in distances from the left edge
298 xa2b2ab = (xa * xa + xb * xb) + xa * xb
299 average = a_l + ( ( a_r - a_l ) * mx &
300 + a_c * ( 3. * ( xb + xa ) - 2. * xa2b2ab ) )
301 else
302 ! This integration of the PPM reconstruction is expressed in distances from the right edge
303 ya = 1. - xa
304 yb = 1. - xb
305 my = 0.5 * ( ya + yb )
306 ya2b2ab = (ya * ya + yb * yb) + ya * yb
307 average = a_r + ( ( a_l - a_r ) * my &
308 + a_c * ( 3. * ( yb + ya ) - 2. * ya2b2ab ) )
309 endif
310
311end function average
312
313!> Deallocate the PPM_CWK reconstruction
314subroutine destroy(this)
315 class(ppm_cwk), intent(inout) :: this !< This reconstruction
316
317 deallocate( this%u_mean, this%ul, this%ur )
318
319end subroutine destroy
320
321!> Checks the PPM_CWK reconstruction for consistency
322logical function check_reconstruction(this, h, u)
323 class(ppm_cwk), intent(in) :: this !< This reconstruction
324 real, intent(in) :: h(*) !< Grid spacing (thickness) [typically H]
325 real, intent(in) :: u(*) !< Cell mean values [A]
326 ! Local variables
327 integer :: k
328
329 check_reconstruction = .false.
330
331 ! Simply checks the internal copy of "u" is exactly equal to "u"
332 do k = 1, this%n
333 if ( abs( this%u_mean(k) - u(k) ) > 0. ) check_reconstruction = .true.
334 enddo
335
336 ! If (u - ul) has the opposite sign from (ur - u), then this cell has an interior extremum
337 do k = 1, this%n
338 if ( ( this%u_mean(k) - this%ul(k) ) * ( this%ur(k) - this%u_mean(k) ) < 0. ) check_reconstruction = .true.
339 enddo
340
341 ! Check bounding of right edges, w.r.t. the cell means
342 do k = 1, this%n-1
343 if ( ( this%ur(k) - this%u_mean(k) ) * ( this%u_mean(k+1) - this%ur(k) ) < 0. ) check_reconstruction = .true.
344 enddo
345
346 ! Check bounding of left edges, w.r.t. the cell means
347 do k = 2, this%n
348 if ( ( this%u_mean(k) - this%ul(k) ) * ( this%ul(k) - this%u_mean(k-1) ) < 0. ) check_reconstruction = .true.
349 enddo
350
351 ! Check bounding of right edges, w.r.t. this cell mean and the next cell left edge
352 do k = 1, this%n-1
353 if ( ( this%ur(k) - this%u_mean(k) ) * ( this%ul(k+1) - this%ur(k) ) < 0. ) check_reconstruction = .true.
354 enddo
355
356 ! Check bounding of left edges, w.r.t. this cell mean and the previous cell right edge
357 do k = 2, this%n
358 if ( ( this%u_mean(k) - this%ul(k) ) * ( this%ul(k) - this%ur(k-1) ) < 0. ) check_reconstruction = .true.
359 enddo
360
361end function check_reconstruction
362
363!> Runs PPM_CWK reconstruction unit tests and returns True for any fails, False otherwise
364logical function unit_tests(this, verbose, stdout, stderr)
365 class(ppm_cwk), intent(inout) :: this !< This reconstruction
366 logical, intent(in) :: verbose !< True, if verbose
367 integer, intent(in) :: stdout !< I/O channel for stdout
368 integer, intent(in) :: stderr !< I/O channel for stderr
369 ! Local variables
370 real, allocatable :: ul(:), ur(:), um(:) ! test values [A]
371 real, allocatable :: ull(:), urr(:) ! test values [A]
372 type(testing) :: test ! convenience functions
373 integer :: k
374
375 call test%set( stdout=stdout ) ! Sets the stdout channel in test
376 call test%set( stderr=stderr ) ! Sets the stderr channel in test
377 call test%set( verbose=verbose ) ! Sets the verbosity flag in test
378
379 if (verbose) write(stdout,'(a)') 'PPM_CWK:unit_tests testing with linear fn'
380
381 call this%init(5)
382 call test%test( this%n /= 5, 'Setting number of levels')
383 allocate( um(5), ul(5), ur(5), ull(5), urr(5) )
384
385 ! Straight line, f(x) = x , or f(K) = 2*K
386 call this%reconstruct( (/2.,2.,2.,2.,2./), (/1.,4.,7.,10.,13./) )
387 call test%real_arr(5, this%u_mean, (/1.,4.,7.,10.,13./), 'Setting cell values')
388 ! Without PLM extrapolation we get l(2)=2 and r(4)=12 due to PLM=0 in boundary cells. -AJA
389 call test%real_arr(5, this%ul, (/1.,2.5,5.5,8.5,13./), 'Left edge values')
390 call test%real_arr(5, this%ur, (/1.,5.5,8.5,11.5,13./), 'Right edge values')
391
392 do k = 1, 5
393 ul(k) = this%f(k, 0.)
394 um(k) = this%f(k, 0.5)
395 ur(k) = this%f(k, 1.)
396 enddo
397 call test%real_arr(5, ul, this%ul, 'Evaluation on left edge')
398 call test%real_arr(5, um, (/1.,4.,7.,10.,13./), 'Evaluation in center')
399 call test%real_arr(5, ur, this%ur, 'Evaluation on right edge')
400
401 do k = 1, 5
402 ul(k) = this%dfdx(k, 0.)
403 um(k) = this%dfdx(k, 0.5)
404 ur(k) = this%dfdx(k, 1.)
405 enddo
406 ! Most of these values are affected by the PLM boundary cells
407 call test%real_arr(5, ul, (/0.,3.,3.,3.,0./), 'dfdx on left edge')
408 call test%real_arr(5, um, (/0.,3.,3.,3.,0./), 'dfdx in center')
409 call test%real_arr(5, ur, (/0.,3.,3.,3.,0./), 'dfdx on right edge')
410
411 do k = 1, 5
412 um(k) = this%average(k, 0.5, 0.75) ! Average from x=0.25 to 0.75 in each cell
413 enddo
414 ! Most of these values are affected by the PLM boundary cells
415 call test%real_arr(5, um, (/1.,4.375,7.375,10.375,13./), 'Return interval average')
416
417 if (verbose) write(stdout,'(a)') 'PPM_CWK:unit_tests testing with parabola'
418
419 ! x = 2 i i=0 at origin
420 ! f(x) = 3/4 x^2 = (2 i)^2
421 ! f[i] = 3/4 ( 2 i - 1 )^2 on centers
422 ! f[I] = 3/4 ( 2 I )^2 on edges
423 ! f[i] = 1/8 [ x^3 ] for means
424 ! edges: 0, 1, 12, 27, 48, 75
425 ! means: 1, 7, 19, 37, 61
426 ! centers: 0.75, 6.75, 18.75, 36.75, 60.75
427 call this%reconstruct( (/2.,2.,2.,2.,2./), (/1.,7.,19.,37.,61./) )
428 do k = 1, 5
429 ul(k) = this%f(k, 0.)
430 um(k) = this%f(k, 0.5)
431 ur(k) = this%f(k, 1.)
432 enddo
433 call test%real_arr(5, ul, (/1.,3.,12.,27.,61./), 'Return left edge')
434 call test%real_arr(5, um, (/1.,6.75,18.75,36.75,61./), 'Return center')
435 call test%real_arr(5, ur, (/1.,12.,27.,48.,61./), 'Return right edge')
436
437 call test%real_scalar( this%x(3,12.), 0., 'f-1(3,12)=0')
438 call test%real_scalar( this%x(3,15.1875), 0.25, 'f-1(3,15.1875)=0.25')
439 call test%real_scalar( this%x(3,18.75), 0.5, 'f-1(3,18.75)=0.5')
440 call test%real_scalar( this%x(3,27.), 1., 'f-1(3,27)=1')
441
442 call this%reconstruct( (/2.,2.,2.,2.,2./), (/-1.,-7.,-19.,-37.,-61./) )
443 call test%real_scalar( this%x(3,-12.), 0., 'f-1(3,-12)=0')
444 call test%real_scalar( this%x(3,-15.1875), 0.25, 'f-1(3,-15.1875)=0.25')
445 call test%real_scalar( this%x(3,-18.75), 0.5, 'f-1(3,-18.75)=0.5')
446 call test%real_scalar( this%x(3,-27.), 1., 'f-1(3,-27)=1')
447
448 ! x = 3 i i=0 at origin
449 ! f(x) = x^2 / 3 = 3 i^2
450 ! f[i] = [ ( 3 i )^3 - ( 3 i - 3 )^3 ] i=1,2,3,4,5
451 ! means: 1, 7, 19, 37, 61
452 ! edges: 0, 3, 12, 27, 48, 75
453 call this%reconstruct( (/3.,3.,3.,3.,3./), (/1.,7.,19.,37.,61./) )
454 do k = 1, 5
455 ul(k) = this%f(k, 0.)
456 um(k) = this%f(k, 0.5)
457 ur(k) = this%f(k, 1.)
458 enddo
459 call test%real_arr(5, ul, (/1.,3.,12.,27.,61./), 'Return left edge')
460 call test%real_arr(5, um, (/1.,6.75,18.75,36.75,61./), 'Return center')
461 call test%real_arr(5, ur, (/1.,12.,27.,48.,61./), 'Return right edge')
462
463 call this%destroy()
464 deallocate( um, ul, ur, ull, urr )
465
466 unit_tests = test%summarize('PPM_CWK:unit_tests')
467
468end function unit_tests
469
470!> \namespace recon1d_ppm_cwk
471!!
472
473end module recon1d_ppm_cwk