Neko 1.99.9
A portable framework for high-order spectral element flow simulations
Loading...
Searching...
No Matches
map_1d.f90
Go to the documentation of this file.
1! Copyright (c) 2023-2026, The Neko Authors
2! All rights reserved.
3!
4! Redistribution and use in source and binary forms, with or without
5! modification, are permitted provided that the following conditions
6! are met:
7!
8! * Redistributions of source code must retain the above copyright
9! notice, this list of conditions and the following disclaimer.
10!
11! * Redistributions in binary form must reproduce the above
12! copyright notice, this list of conditions and the following
13! disclaimer in the documentation and/or other materials provided
14! with the distribution.
15!
16! * Neither the name of the authors nor the names of its
17! contributors may be used to endorse or promote products derived
18! from this software without specific prior written permission.
19!
20! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21! "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22! LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23! FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24! COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25! INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26! BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27! LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29! LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30! ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31! POSSIBILITY OF SUCH DAMAGE.
32!
33!
36module map_1d
38 use num_types, only : rp
39 use space, only : space_t
40 use dofmap, only : dofmap_t
41 use gather_scatter, only : gs_op_add
42 use mesh, only : mesh_t
46 use coefs, only : coef_t
47 use field_list, only : field_list_t
48 use matrix, only : matrix_t
49 use vector, only : vector_ptr_t
50 use utils, only : neko_error, neko_warning
51 use math, only : glmax, glmin, glimax, glimin, cmult, add2s1, col2
52 use mpi_f08, only : mpi_allreduce, mpi_sum, mpi_barrier, mpi_in_place
53 use, intrinsic :: iso_c_binding
54 implicit none
55 private
56
96 type, public :: map_1d_t
100 integer, allocatable :: dir_el(:)
103 integer, allocatable :: el_lvl(:)
107 integer, allocatable :: pt_lvl(:, :, :, :)
109 integer :: n_el_lvls
113 integer :: n_gll_lvls
115 type(dofmap_t), pointer :: dof => null()
117 type(coef_t), pointer :: coef => null()
119 type(mesh_t), pointer :: msh => null()
122 integer :: dir
126 real(kind=rp) :: tol = 1e-7
129 real(kind=rp), allocatable :: volume_per_gll_lvl(:)
130 contains
132 procedure, pass(this) :: init_int => map_1d_init
133 procedure, pass(this) :: init_char => map_1d_init_char
134 generic :: init => init_int, init_char
136 procedure, pass(this) :: free => map_1d_free
138 procedure, pass(this) :: average_planes_fld_lst => &
140 procedure, pass(this) :: average_planes_vec_ptr => &
142 generic :: average_planes => average_planes_fld_lst, average_planes_vec_ptr
143 end type map_1d_t
144
145
146contains
147
148 subroutine map_1d_init(this, coef, dir, tol)
149 class(map_1d_t) :: this
150 type(coef_t), intent(inout), target :: coef
151 integer, intent(in) :: dir
152 real(kind=rp), intent(in) :: tol
153 integer :: nelv, lx, n, i, e, lvl, ierr
154 real(kind=rp), contiguous, pointer :: line(:, :, :, :)
155 real(kind=rp), allocatable :: min_vals(:, :, :, :)
156 real(kind=rp), allocatable :: min_temp(:, :, :, :)
157 type(c_ptr) :: min_vals_d = c_null_ptr
158 real(kind=rp) :: el_dim(3, 3), glb_min, glb_max, el_min, abs_tol
159
160 call this%free()
161
162 if (neko_bcknd_device .eq. 1) then
163 if (pe_rank .eq. 0) then
164 call neko_warning('map_1d does not copy indices to device, ' // &
165 ' but ok if used on cpu and for io')
166 end if
167 end if
168
169 this%dir = dir
170 this%tol = tol
171 this%dof => coef%dof
172 this%coef => coef
173 this%msh => coef%msh
174 nelv = this%msh%nelv
175 lx = this%dof%Xh%lx
176 n = this%dof%size()
177
178 if (dir .eq. 1) then
179 line => this%dof%x%x
180 else if (dir .eq. 2) then
181 line => this%dof%y%x
182 else if (dir .eq. 3) then
183 line => this%dof%z%x
184 else
185 call neko_error('Invalid dir for geopmetric comm')
186 end if
187 allocate(this%dir_el(nelv))
188 allocate(this%el_lvl(nelv))
189 allocate(this%pt_lvl(lx, lx, lx, nelv))
190 allocate(min_vals(lx, lx, lx, nelv))
191 allocate(min_temp(lx, lx, lx, nelv))
192 call mpi_barrier(neko_comm)
193 if (neko_bcknd_device .eq. 1) then
194
195 call device_map(min_vals, min_vals_d, n)
196 end if
197 call mpi_barrier(neko_comm)
198
199 do i = 1, nelv
200 ! Store which direction r,s,t corresponds to specified direction, x,y,z
201 ! we assume elements are stacked on each other...
202 ! Check which one of the normalized vectors are closest to dir
203 ! If we want to incorporate other directions, we should look here
204
205 ! This follows the point ordering defined in hex.f90
206 el_dim(1, :) = abs(this%msh%elements(i)%e%pts(1)%p%x - &
207 this%msh%elements(i)%e%pts(2)%p%x)
208 el_dim(1, :) = el_dim(1, :)/norm2(el_dim(1, :))
209 el_dim(2, :) = abs(this%msh%elements(i)%e%pts(1)%p%x - &
210 this%msh%elements(i)%e%pts(3)%p%x)
211 el_dim(2, :) = el_dim(2, :)/norm2(el_dim(2, :))
212 el_dim(3, :) = abs(this%msh%elements(i)%e%pts(1)%p%x - &
213 this%msh%elements(i)%e%pts(5)%p%x)
214 el_dim(3, :) = el_dim(3, :)/norm2(el_dim(3, :))
215 ! Checks which directions in rst the xyz corresponds to
216 ! 1 corresponds to r, 2 to s, 3 to t and are stored in dir_el
217 this%dir_el(i) = maxloc(el_dim(:, this%dir), dim = 1)
218 end do
219
220 glb_min = glmin(line, n)
221 glb_max = glmax(line, n)
222
223 ! The propagation below moves the element minima through the mesh with
224 ! gather-scatter sums scaled by coef%mult. That arithmetic is not exact:
225 ! coef%mult is 1/3 at some vertices of unstructured meshes. With the
226 ! default floating point model of ifx even 1/1 is rounded. A propagated
227 ! minimum can therefore differ from glb_min by a few roundoff errors of
228 ! the coordinate magnitude. A tolerance relative to glb_min would demand
229 ! exact equality when glb_min is zero, so compare with an absolute
230 ! tolerance instead. The user tolerance is relative to the extent of the
231 ! coordinates, so that it does not depend on where the origin is, and
232 ! the roundoff floor is relative to their magnitude, which is what the
233 ! propagation error scales with.
234 abs_tol = max(this%tol * (glb_max - glb_min), &
235 8.0_rp * epsilon(1.0_rp) * max(abs(glb_min), abs(glb_max)))
236
237 i = 1
238 this%el_lvl = -1
239 ! Check what the minimum value in each element and put in min_vals
240 do e = 1, nelv
241 el_min = minval(line(:, :, :, e))
242 ! Check if this element is on the bottom,
243 ! in this case assign el_lvl = i = 1
244 if (abs(el_min - glb_min) .le. abs_tol) then
245 el_min = glb_min
246 if (this%el_lvl(e) .eq. -1) this%el_lvl(e) = i
247 end if
248 min_vals(:, :, :, e) = el_min
249 end do
250
251 ! While loop where at each iteration the global maximum value
252 ! propagates down one level.
253 ! When the minimum value has propagated to the highest level this stops.
254 ! Only works when the bottom plate of the domain is flat.
255 do while (abs(glmax(min_vals, n) - glb_min) .gt. abs_tol)
256
257 ! This is the assigned level
258 i = i + 1
259 ! There cannot be more levels than elements, so a longer propagation
260 ! means the minimum never reaches some elements within abs_tol. Leave
261 ! the loop and report that below.
262 if (i .gt. this%msh%glb_nelv) exit
263
264 do e = 1, nelv
265 !Sets the value at the bottom of each element to glb_max
266 if (this%dir_el(e) .eq. 1) then
267 if (line(1, 1, 1, e) .gt. line(lx, 1, 1, e)) then
268 min_vals(lx, :, :, e) = glb_max
269 else
270 min_vals(1, :, :, e) = glb_max
271 end if
272 end if
273 if (this%dir_el(e) .eq. 2) then
274 if (line(1, 1, 1, e) .gt. line(1, lx, 1, e)) then
275 min_vals(:, lx, :, e) = glb_max
276 else
277 min_vals(:, 1, :, e) = glb_max
278 end if
279 end if
280 if (this%dir_el(e) .eq. 3) then
281 if (line(1, 1, 1, e) .gt. line(1, 1, lx, e)) then
282 min_vals(:, :, lx, e) = glb_max
283 else
284 min_vals(:, :, 1, e) = glb_max
285 end if
286 end if
287 end do
288
289 !Make sketchy min as GS_OP_MIN is not supported with device mpi
290 min_temp = min_vals
291
292 if (neko_bcknd_device .eq. 1) then
293 call device_memcpy(min_vals, min_vals_d, n, host_to_device, &
294 sync = .false.)
295 end if
296
297 !Propagates the minimum value along the element boundary.
298 call coef%gs_h%op(min_vals, n, gs_op_add)
299
300 if (neko_bcknd_device .eq. 1) then
301 call device_memcpy(min_vals, min_vals_d, n, device_to_host, &
302 sync = .true.)
303 end if
304
305 !Obtain average along boundary
306 call col2(min_vals, coef%mult, n)
307 call cmult(min_temp, -1.0_rp, n)
308 call add2s1(min_vals, min_temp, 2.0_rp, n)
309
310 !Checks the new minimum value on each element
311 !Assign this value to all points in this element in min_val
312 !If the element has not already been assigned a level,
313 !and it has obtained the minval, set el_lvl = i
314 do e = 1, nelv
315 el_min = minval(min_vals(:, :, :, e))
316 if (abs(el_min - glb_min) .le. abs_tol) then
317 ! Store exactly glb_min so that the roundoff of this propagation
318 ! step does not accumulate into the next level.
319 el_min = glb_min
320 if (this%el_lvl(e) .eq. -1) this%el_lvl(e) = i
321 end if
322 min_vals(:, :, :, e) = el_min
323 end do
324 end do
325
326 if (allocated(min_vals)) then
327 if (c_associated(min_vals_d)) call device_unmap(min_vals, min_vals_d)
328 deallocate(min_vals)
329 end if
330
331 if (allocated(min_temp)) deallocate(min_temp)
332
333 ! An unassigned element would give negative point levels, which index
334 ! volume_per_gll_lvl and the averaged output out of bounds. Return after
335 ! the error, since a unit test with errors redirected to pFUnit
336 ! exceptions continues past neko_error.
337 if (glimin(this%el_lvl, nelv) .lt. 1) then
338 call neko_error('map_1d: some elements were not assigned a level, ' // &
339 'check that the mesh is layered in the requested direction')
340 return
341 end if
342 this%n_el_lvls = glimax(this%el_lvl, nelv)
343 this%n_gll_lvls = this%n_el_lvls*lx
344
345 !Numbers the points in each element based on the element level
346 !and its orientation
347 do e = 1, nelv
348 do i = 1, lx
349 lvl = lx * (this%el_lvl(e) - 1) + i
350 if (this%dir_el(e) .eq. 1) then
351 if (line(1, 1, 1, e) .gt. line(lx, 1, 1, e)) then
352 this%pt_lvl(lx-i+1, :, :, e) = lvl
353 else
354 this%pt_lvl(i, :, :, e) = lvl
355 end if
356 end if
357 if (this%dir_el(e) .eq. 2) then
358 if (line(1, 1, 1, e) .gt. line(1, lx, 1, e)) then
359 this%pt_lvl(:, lx-i+1, :, e) = lvl
360 else
361 this%pt_lvl(:, i, :, e) = lvl
362 end if
363 end if
364 if (this%dir_el(e) .eq. 3) then
365 if (line(1, 1, 1, e) .gt. line(1, 1, lx, e)) then
366 this%pt_lvl(:, :, lx-i+1, e) = lvl
367 else
368 this%pt_lvl(:, :, i, e) = lvl
369 end if
370 end if
371 end do
372 end do
373
374 allocate(this%volume_per_gll_lvl(this%n_gll_lvls))
375
376 this%volume_per_gll_lvl = 0.0_rp
377
378 do i = 1, n
379 this%volume_per_gll_lvl(this%pt_lvl(i, 1, 1, 1)) = &
380 this%volume_per_gll_lvl(this%pt_lvl(i, 1, 1, 1)) + &
381 coef%B(i, 1, 1, 1)
382 end do
383
384 call mpi_allreduce(mpi_in_place, this%volume_per_gll_lvl, this%n_gll_lvls, &
385 mpi_real_precision, mpi_sum, neko_comm, ierr)
386
387 end subroutine map_1d_init
388
389 subroutine map_1d_init_char(this, coef, dir, tol)
390 class(map_1d_t) :: this
391 type(coef_t), intent(inout), target :: coef
392 character(len=*), intent(in) :: dir
393 real(kind=rp), intent(in) :: tol
394 integer :: idir
395
396 if (trim(dir) .eq. 'yz' .or. trim(dir) .eq. 'zy') then
397 idir = 1
398 else if (trim(dir) .eq. 'xz' .or. trim(dir) .eq. 'zx') then
399 idir = 2
400 else if (trim(dir) .eq. 'xy' .or. trim(dir) .eq. 'yx') then
401 idir = 3
402 else
403 call neko_error('homogenous direction not supported')
404 end if
405
406 call this%init(coef, idir, tol)
407
408 end subroutine map_1d_init_char
409
411 subroutine map_1d_free(this)
412 class(map_1d_t) :: this
413
414 if (allocated(this%dir_el)) deallocate(this%dir_el)
415 if (allocated(this%el_lvl)) deallocate(this%el_lvl)
416 if (allocated(this%pt_lvl)) deallocate(this%pt_lvl)
417 if (associated(this%dof)) nullify(this%dof)
418 if (associated(this%msh)) nullify(this%msh)
419 if (associated(this%coef)) nullify(this%coef)
420 if (allocated(this%volume_per_gll_lvl)) deallocate(this%volume_per_gll_lvl)
421 this%dir = 0
422 this%n_el_lvls = 0
423 this%n_gll_lvls = 0
424
425 end subroutine map_1d_free
426
427
433 subroutine map_1d_average_field_list(this, avg_planes, field_list)
434 class(map_1d_t), intent(inout) :: this
435 type(field_list_t), intent(inout) :: field_list
436 type(matrix_t), intent(inout) :: avg_planes
437 integer :: n, ierr, j, i
438 real(kind=rp) :: coord
439 call avg_planes%free()
440 call avg_planes%init(this%n_gll_lvls, field_list%size() + 1)
441 avg_planes = 0.0_rp
442 !ugly way of getting coordinates, computes average
443 n = this%dof%size()
444 do i = 1, n
445 if (this%dir .eq. 1) coord = this%dof%x%x(i,1,1,1)
446 if (this%dir .eq. 2) coord = this%dof%y%x(i,1,1,1)
447 if (this%dir .eq. 3) coord = this%dof%z%x(i,1,1,1)
448 avg_planes%x(this%pt_lvl(i,1,1,1), 1) = &
449 avg_planes%x(this%pt_lvl(i,1,1,1), 1) + &
450 coord * this%coef%B(i,1,1,1) / &
451 this%volume_per_gll_lvl(this%pt_lvl(i,1,1,1))
452 end do
453 do j = 2, field_list%size() + 1
454 do i = 1, n
455 avg_planes%x(this%pt_lvl(i,1,1,1), j) = &
456 avg_planes%x(this%pt_lvl(i,1,1,1), j) + &
457 field_list%items(j-1)%ptr%x(i,1,1,1) * this%coef%B(i,1,1,1) &
458 /this%volume_per_gll_lvl(this%pt_lvl(i,1,1,1))
459 end do
460 end do
461 if (pe_size .gt. 1) then
462 call mpi_allreduce(mpi_in_place, avg_planes%x, &
463 (field_list%size() + 1) * this%n_gll_lvls, mpi_real_precision, &
464 mpi_sum, neko_comm, ierr)
465 end if
466
467 end subroutine map_1d_average_field_list
468
474 subroutine map_1d_average_vector_ptr(this, avg_planes, vector_ptr)
475 class(map_1d_t), intent(inout) :: this
476 !Observe is an array...
477 type(vector_ptr_t), intent(inout) :: vector_ptr(:)
478 type(matrix_t), intent(inout) :: avg_planes
479 integer :: n, ierr, j, i
480 real(kind=rp) :: coord
481
482 call avg_planes%free()
483 call avg_planes%init(this%n_gll_lvls, size(vector_ptr) + 1)
484 !ugly way of getting coordinates, computes average
485 avg_planes = 0.0_rp
486
487 n = this%dof%size()
488 do i = 1, n
489 if (this%dir .eq. 1) coord = this%dof%x%x(i,1,1,1)
490 if (this%dir .eq. 2) coord = this%dof%y%x(i,1,1,1)
491 if (this%dir .eq. 3) coord = this%dof%z%x(i,1,1,1)
492 avg_planes%x(this%pt_lvl(i,1,1,1), 1) = &
493 avg_planes%x(this%pt_lvl(i,1,1,1), 1) + &
494 coord * this%coef%B(i,1,1,1) / &
495 this%volume_per_gll_lvl(this%pt_lvl(i,1,1,1))
496 end do
497 do j = 2, size(vector_ptr) + 1
498 do i = 1, n
499 avg_planes%x(this%pt_lvl(i,1,1,1), j) = &
500 avg_planes%x(this%pt_lvl(i,1,1,1), j) + &
501 vector_ptr(j-1)%ptr%x(i)*this%coef%B(i,1,1,1) &
502 /this%volume_per_gll_lvl(this%pt_lvl(i,1,1,1))
503 end do
504 end do
505 call mpi_allreduce(mpi_in_place, avg_planes%x, &
506 (size(vector_ptr) + 1) * this%n_gll_lvls, mpi_real_precision, &
507 mpi_sum, neko_comm, ierr)
508
509
510 end subroutine map_1d_average_vector_ptr
511
512end module map_1d
Map a Fortran array to a device (allocate and associate)
Definition device.F90:83
Copy data between host and device (or device and device)
Definition device.F90:72
Unmap a Fortran array from a device (deassociate and free)
Definition device.F90:89
Coefficients.
Definition coef.f90:34
Definition comm.F90:1
type(mpi_datatype), public mpi_real_precision
MPI type for working precision of REAL types.
Definition comm.F90:54
integer, public pe_size
MPI size of communicator.
Definition comm.F90:62
integer, public pe_rank
MPI rank.
Definition comm.F90:59
type(mpi_comm), public neko_comm
MPI communicator.
Definition comm.F90:46
Device abstraction, common interface for various accelerators.
Definition device.F90:34
integer, parameter, public host_to_device
Definition device.F90:48
integer, parameter, public device_to_host
Definition device.F90:48
Defines a mapping of the degrees of freedom.
Definition dofmap.f90:35
Gather-scatter.
Creates a 1d GLL point map along a specified direction based on the connectivity in the mesh.
Definition map_1d.f90:36
subroutine map_1d_init(this, coef, dir, tol)
Definition map_1d.f90:149
subroutine map_1d_free(this)
Destructor.
Definition map_1d.f90:412
subroutine map_1d_average_field_list(this, avg_planes, field_list)
Computes average if field list in two directions and outputs matrix with averaged values avg_planes c...
Definition map_1d.f90:434
subroutine map_1d_init_char(this, coef, dir, tol)
Definition map_1d.f90:390
subroutine map_1d_average_vector_ptr(this, avg_planes, vector_ptr)
Computes average of vector_pt in two directions and outputs matrix with averaged values avg_planes co...
Definition map_1d.f90:475
Definition math.f90:60
subroutine, public cmult(a, c, n)
Multiplication by constant c .
Definition math.f90:508
subroutine, public add2s1(a, b, c1, n)
Vector addition with scalar multiplication (multiplication on first argument)
Definition math.f90:1020
integer function, public glimin(a, n)
Min of an integer vector of length n.
Definition math.f90:731
subroutine, public col2(a, b, n)
Vector multiplication .
Definition math.f90:1085
real(kind=rp) function, public glmax(a, n)
Max of a vector of length n.
Definition math.f90:654
integer function, public glimax(a, n)
Max of an integer vector of length n.
Definition math.f90:693
real(kind=rp) function, public glmin(a, n)
Min of a vector of length n.
Definition math.f90:712
Defines a matrix.
Definition matrix.f90:34
Defines a mesh.
Definition mesh.f90:34
Build configurations.
integer, parameter neko_bcknd_device
integer, parameter, public rp
Global precision used in computations.
Definition num_types.f90:14
Defines a function space.
Definition space.f90:34
Utilities.
Definition utils.f90:35
subroutine, public neko_warning(warning_msg)
Reports a warning to standard output.
Definition utils.f90:454
Defines a vector.
Definition vector.f90:34
Coefficients defined on a given (mesh, ) tuple. Arrays use indices (i,j,k,e): element e,...
Definition coef.f90:136
field_list_t, To be able to group fields together
Map every GLL point in the mesh to a level in one physical direction. Can be used to average across t...
Definition map_1d.f90:96
The function space for the SEM solution fields.
Definition space.f90:64
#define max(a, b)
Definition tensor.cu:40