Recon1d_PLM_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 Linear Method 1D reconstruction
6!!
7!! This implementation of PLM follows Colella and Woodward, 1984 \cite colella1984, with cells
8!! resorting to PCM for extrema including the first and last cells in column.
9!! The cell-wise reconstructions are limited so that the edge values (which are also the extrema
10!! in a cell) are bounded by the neighboring cell means.
11!! This does not yield monotonic profiles for the general remapping problem.
12module recon1d_plm_cw
13
14use recon1d_type, only : recon1d, testing
15
16implicit none ; private
17
18public plm_cw, testing
19
20!> PLM reconstruction following Colella and Woodward, 1984
21!!
22!! The source for the methods ultimately used by this class are:
23!! - init() *locally defined
24!! - reconstruct() *locally defined
25!! - average() *locally defined
26!! - f() *locally defined
27!! - dfdx() *locally defined
28!! - x() *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) :: plm_cw
36
37 real, allocatable :: ul(:) !< Left edge value [A]
38 real, allocatable :: ur(:) !< Right edge value [A]
39
40contains
41 !> Implementation of the PLM_CW initialization
42 procedure :: init => init
43 !> Implementation of the PLM_CW reconstruction
44 procedure :: reconstruct => reconstruct
45 !> Implementation of the PLM_CW average over an interval [A]
46 procedure :: average => average
47 !> Implementation of evaluating the PLM_CW reconstruction at a point [A]
48 procedure :: f => f
49 !> Implementation of the derivative of the PLM_CW reconstruction at a point [A]
50 procedure :: dfdx => dfdx
51 !> Implementation of solver for x: f(x)=t
52 procedure :: x => x
53 !> Implementation of deallocation for PLM_CW
54 procedure :: destroy => destroy
55 !> Implementation of check reconstruction for the PLM_CW reconstruction
57 !> Implementation of unit tests for the PLM_CW reconstruction
58 procedure :: unit_tests => unit_tests
59
60 !> Duplicate interface to init()
61 procedure :: init_parent => init
62 !> Duplicate interface to reconstruct()
63 procedure :: reconstruct_parent => reconstruct
64
65end type plm_cw
66
67contains
68
69!> Initialize a 1D PLM reconstruction for n cells
70subroutine init(this, n, h_neglect, check)
71 class(plm_cw), intent(out) :: this !< This reconstruction
72 integer, intent(in) :: n !< Number of cells in this column
73 real, optional, intent(in) :: h_neglect !< A negligibly small width used in cell reconstructions [H]
74 logical, optional, intent(in) :: check !< If true, enable some consistency checking
75
76 this%n = n
77
78 allocate( this%u_mean(n) )
79 allocate( this%ul(n) )
80 allocate( this%ur(n) )
81
82 this%h_neglect = tiny( this%u_mean(1) )
83 if (present(h_neglect)) this%h_neglect = h_neglect
84 this%check = .false.
85 if (present(check)) this%check = check
86
87end subroutine init
88
89!> Calculate a 1D PLM reconstructions based on h(:) and u(:)
90subroutine reconstruct(this, h, u)
91 class(plm_cw), intent(inout) :: this !< This reconstruction
92 real, intent(in) :: h(*) !< Grid spacing (thickness) [typically H]
93 real, intent(in) :: u(*) !< Cell mean values [A]
94 ! Local variables
95 real :: slp ! The PLM slopes (difference across cell) [A]
96 real :: sigma_l, sigma_c, sigma_r ! Left, central and right slope estimates as
97 ! differences across the cell [A]
98 real :: u_min, u_max ! Minimum and maximum value across cell [A]
99 real :: u_l, u_r, u_c ! Left, right, and center values [A]
100 real :: h_l, h_c, h_r ! Thickness of left, center and right cells [H]
101 real :: h_c0 ! Thickness of center with h_neglect added [H]
102 integer :: k, n
103
104 n = this%n
105
106 ! Loop over all cells
107 do k = 1, n
108 this%u_mean(k) = u(k)
109 enddo
110
111 ! Boundary cells use PCM
112 this%ul(1) = u(1)
113 this%ur(1) = u(1)
114
115 ! Loop over interior cells
116 do k = 2, n-1
117 u_l = u(k-1)
118 u_c = u(k)
119 u_r = u(k+1)
120
121 ! Side differences
122 sigma_r = u_r - u_c
123 sigma_l = u_c - u_l
124
125 h_l = h(k-1)
126 h_c = h(k)
127 h_r = h(k+1)
128 ! Avoids division by zero
129 h_c0 = h_c + this%h_neglect
130
131 ! This is the second order slope given by equation 1.7 of
132 ! Piecewise Parabolic Method, Colella and Woodward (1984),
133 ! http://dx.doi.org/10.1016/0021-991(84)90143-8.
134 ! For uniform resolution it simplifies to ( u_r - u_l )/2 .
135 sigma_c = ( h_c / ( h_c0 + ( h_l + h_r ) ) ) * ( &
136 ( 2.*h_l + h_c ) / ( h_r + h_c0 ) * sigma_r &
137 + ( 2.*h_r + h_c ) / ( h_l + h_c0 ) * sigma_l )
138
139 ! Limit slope so that reconstructions are bounded by neighbors
140 u_min = min( u_l, u_c, u_r )
141 u_max = max( u_l, u_c, u_r )
142
143 if ( (sigma_l * sigma_r) > 0.0 ) then
144 ! This limits the slope so that the edge values are bounded by the two cell averages spanning the edge
145 slp = sign( min( abs(sigma_c), 2.*min( u_c - u_min, u_max - u_c ) ), sigma_c )
146 else
147 ! Extrema in the mean values require a PCM reconstruction
148 slp = 0.0
149 endif
150
151 ! Left edge
152 u_min = min( u_c, u_l )
153 u_max = max( u_c, u_l )
154 u_l = u_c - 0.5 * slp
155 this%ul(k) = max( min( u_l, u_max), u_min )
156
157 ! Right edge
158 u_min = min( u_c, u_r )
159 u_max = max( u_c, u_r )
160 u_r = u_c + 0.5 * slp
161 this%ur(k) = max( min( u_r, u_max), u_min )
162 enddo
163
164 ! Boundary cells use PCM
165 this%ul(n) = u(n)
166 this%ur(n) = u(n)
167
168end subroutine reconstruct
169
170!> Value of PLM_CW reconstruction at a point in cell k [A]
171real function f(this, k, x)
172 class(plm_cw), intent(in) :: this !< This reconstruction
173 integer, intent(in) :: k !< Cell number
174 real, intent(in) :: x !< Non-dimensional position within element [nondim]
175 real :: xc ! Bounded version of x [nondim]
176 real :: du ! Difference across cell [A]
177 real :: u_a, u_b ! Two estimate of f [A]
178
179 du = this%ur(k) - this%ul(k)
180 xc = max( 0., min( 1., x ) )
181
182 ! This expression for u_a can overshoot u_r but is good for x<<1
183 u_a = this%ul(k) + du * xc
184 ! This expression for u_b can overshoot u_l but is good for 1-x<<1
185 u_b = this%ur(k) + du * ( xc - 1. )
186
187 ! Since u_a and u_b are both bounded, this will perserve uniformity
188 f = 0.5 * ( u_a + u_b )
189
190end function f
191
192!> Derivative of PLM_CW reconstruction at a point in cell k [A]
193real function dfdx(this, k, x)
194 class(plm_cw), intent(in) :: this !< This reconstruction
195 integer, intent(in) :: k !< Cell number
196 real, intent(in) :: x !< Non-dimensional position within element [nondim]
197
198 dfdx = this%ur(k) - this%ul(k)
199
200end function dfdx
201
202!> Solver for x such that f(x)=t
203real function x(this, k, t)
204 class(plm_cw), intent(in) :: this !< This reconstruction
205 integer, intent(in) :: k !< Cell number
206 real, intent(in) :: t !< Value to solve for [A]
207 real :: slp ! Difference across cell [A]
208
209 slp = this%ur(k) - this%ul(k)
210 if ( abs(slp) > 0. ) then
211 x = ( t - this%ul(k) ) / slp
212 x = max( 0., min( x, 1. ) )
213 else
214 slp = this%ul(min(k+1,this%n)) - this%ur(max(k-1,1))
215 if ( abs(slp) > 0. ) slp = sign(1., slp)
216 x = 0.5 ! Fall back if t==u_mean
217 ! if t>u_mean & slp=1 then x=1
218 ! if t<u_mean & slp=1 then x=0
219 ! if t>u_mean & slp=-1 then x=0
220 ! if t<u_mean & slp=-1 then x=1
221 ! if t=u_mean then slp=0
222 ! if slp=0 then x=0.5
223 if ( abs(t - this%u_mean(k)) > 0. ) x = 0.5 + slp * sign(0.5, t - this%u_mean(k))
224 endif
225end function x
226
227!> Average between xa and xb for cell k of a 1D PLM reconstruction [A]
228real function average(this, k, xa, xb)
229 class(plm_cw), intent(in) :: this !< This reconstruction
230 integer, intent(in) :: k !< Cell number
231 real, intent(in) :: xa !< Start of averaging interval on element (0 to 1)
232 real, intent(in) :: xb !< End of averaging interval on element (0 to 1)
233 real :: xmab ! Mid-point between xa and xb (0 to 1)
234 real :: u_a, u_b ! Values at xa and xb [A]
235
236 ! This form is not guaranteed to be bounded by {ul,ur}
237! u_a = this%ul(k) * ( 1. - xa ) + this%ur(k) * xa
238! u_b = this%ul(k) * ( 1. - xb ) + this%ur(k) * xb
239! average = 0.5 * ( u_a + u_b )
240
241 ! Mid-point between xa and xb
242 xmab = 0.5 * ( xa + xb )
243
244 ! The following expression is exact at xmab=0 and xmab=1,
245 ! i.e. gives the numerically correct values.
246 ! It is not obvious that the expression is monotonic but according to
247 ! https://math.stackexchange.com/questions/907329/accurate-floating-point-linear-interpolation
248 ! it will be for the default rounding behavior. Otherwise is it
249 ! then possible this expression can be outside the range of ul and ur?
250! average = this%ul(k) * ( 1. - xmab ) + this%ur(k) * xmab
251 ! Emperically it fails the uniform value test
252
253 ! The following is more complicated but seems to ensure being within bounds.
254 ! This expression for u_a can overshoot u_r but is good for xmab<<1
255 u_a = this%ul(k) + ( this%ur(k) - this%ul(k) ) * xmab
256 ! This expression for u_b can overshoot u_l but is good for 1-xmab<<1
257 u_b = this%ur(k) + ( this%ul(k) - this%ur(k) ) * ( 1. - xmab )
258 ! Replace xmab with -1 for xmab<0.5, 1 for xmab>=0.5
259! xmab = sign(1., xmab-0.5)
260 ! Select either u_a or u_b, depending whether mid-point of xa, xb is smaller/larger than 0.5
261! average = xmab * u_b + ( 1. - xmab ) * u_a
262
263 ! Since u_a and u_b are both bounded, this will perserve uniformity but will the
264 ! sum be bounded? Emperically it seems to work...
265 average = 0.5 * ( u_a + u_b )
266
267end function average
268
269!> Deallocate the PLM reconstruction
270subroutine destroy(this)
271 class(plm_cw), intent(inout) :: this !< This reconstruction
272
273 deallocate( this%u_mean, this%ul, this%ur )
274
275end subroutine destroy
276
277!> Checks the PLM_CW reconstruction for consistency
278logical function check_reconstruction(this, h, u)
279 class(plm_cw), intent(in) :: this !< This reconstruction
280 real, intent(in) :: h(*) !< Grid spacing (thickness) [typically H]
281 real, intent(in) :: u(*) !< Cell mean values [A]
282 ! Local variables
283 integer :: k
284
285 check_reconstruction = .false.
286
287 do k = 1, this%n
288 if ( abs( this%u_mean(k) - u(k) ) > 0. ) check_reconstruction = .true.
289 enddo
290
291 ! Check the cell reconstruction is monotonic within each cell (it should be as a straight line)
292 do k = 1, this%n
293 if ( ( this%u_mean(k) - this%ul(k) ) * ( this%ur(k) - this%u_mean(k) ) < 0. ) check_reconstruction = .true.
294 enddo
295
296 ! Check the cell is a straight line (to within machine precision)
297 do k = 1, this%n
298 if ( abs(2. * this%u_mean(k) - ( this%ul(k) + this%ur(k) )) > epsilon(this%u_mean(1)) * &
299 max(abs(2. * this%u_mean(k)), abs(this%ul(k)), abs(this%ur(k))) ) check_reconstruction = .true.
300 enddo
301
302 ! Check bounding of right edges, w.r.t. the cell means
303 do k = 1, this%n-1
304 if ( ( this%ur(k) - this%u_mean(k) ) * ( this%u_mean(k+1) - this%ur(k) ) < 0. ) check_reconstruction = .true.
305 enddo
306
307 ! Check bounding of left edges, w.r.t. the cell means
308 do k = 2, this%n
309 if ( ( this%u_mean(k) - this%ul(k) ) * ( this%ul(k) - this%u_mean(k-1) ) < 0. ) check_reconstruction = .true.
310 enddo
311
312 ! PLM is not globally monotonic (expected)
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 PLM reconstruction unit tests and returns True for any fails, False otherwise
327logical function unit_tests(this, verbose, stdout, stderr)
328 class(plm_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
341
342 call this%init(3)
343 call test%test( this%n /= 3, 'Setting number of levels')
344 allocate( um(3), ul(3), ur(3), ull(3), urr(3) )
345
346 call this%reconstruct( (/2.,2.,2./), (/1.,3.,5./) )
347 call test%real_arr(3, this%u_mean, (/1.,3.,5./), 'Setting cell values')
348
349 do k = 1, 3
350 ul(k) = this%f(k, 0.)
351 um(k) = this%f(k, 0.5)
352 ur(k) = this%f(k, 1.)
353 enddo
354 call test%real_arr(3, ul, (/1.,2.,5./), 'Evaluation on left edge')
355 call test%real_arr(3, um, (/1.,3.,5./), 'Evaluation in center')
356 call test%real_arr(3, ur, (/1.,4.,5./), 'Evaluation on right edge')
357
358 do k = 1, 3
359 ul(k) = this%dfdx(k, 0.)
360 um(k) = this%dfdx(k, 0.5)
361 ur(k) = this%dfdx(k, 1.)
362 enddo
363 call test%real_arr(3, ul, (/0.,2.,0./), 'dfdx on left edge')
364 call test%real_arr(3, um, (/0.,2.,0./), 'dfdx in center')
365 call test%real_arr(3, ur, (/0.,2.,0./), 'dfdx on right edge')
366
367 call test%real_scalar( this%x(1,0.), 0., 'f-1(1,0)=0')
368 call test%real_scalar( this%x(1,1.), 0.5, 'f-1(1,1)=0.5')
369 call test%real_scalar( this%x(1,3.), 1., 'f-1(1,3)=1')
370 call test%real_scalar( this%x(2,1.), 0., 'f-1(2,1)=0')
371 call test%real_scalar( this%x(2,3.), 0.5, 'f-1(2,3)=0.5')
372 call test%real_scalar( this%x(2,5.), 1., 'f-1(2,5)=1')
373 call test%real_scalar( this%x(3,3.), 0., 'f-1(3,3)=0')
374 call test%real_scalar( this%x(3,5.), 0.5, 'f-1(3,5)=0.5')
375 call test%real_scalar( this%x(3,7.), 1., 'f-1(3,7)=1')
376
377 do k = 1, 3
378 um(k) = this%average(k, 0.5, 0.75) ! Average from x=0.25 to 0.75 in each cell
379 enddo
380 call test%real_arr(3, um, (/1.,3.25,5./), 'Return interval average')
381
382 call this%destroy()
383 deallocate( um, ul, ur, ull, urr )
384
385 allocate( um(4), ul(4), ur(4) )
386 call this%init(4)
387
388 ! These values lead to non-monotonic reconstuctions which are
389 ! valid for transport problems but not always appropriate for
390 ! remapping to arbitrary resolution grids.
391 ! The O(h^2) slopes are -, 2, 2, - and the limited
392 ! slopes are 0, 1, 1, 0 so the everywhere the reconstructions
393 ! are bounded by neighbors but ur(2) and ul(3) are out-of-order.
394 call this%reconstruct( (/1.,1.,1.,1./), (/0.,3.,4.,7./) )
395 do k = 1, 4
396 ul(k) = this%f(k, 0.)
397 ur(k) = this%f(k, 1.)
398 enddo
399 call test%real_arr(4, ul, (/0.,2.,3.,7./), 'Evaluation on left edge')
400 call test%real_arr(4, ur, (/0.,4.,5.,7./), 'Evaluation on right edge')
401
402 deallocate( um, ul, ur )
403
404 unit_tests = test%summarize('PLM_CW:unit_tests')
405
406end function unit_tests
407
408!> \namespace recon1d_plm_cw
409!!
410
411end module recon1d_plm_cw