Neko 1.99.3
A portable framework for high-order spectral element flow simulations
Loading...
Searching...
No Matches
field_subsampler.f90
Go to the documentation of this file.
1! Copyright (c) 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!
35 use num_types, only : rp
36 use field, only : field_t
37 use coefs, only : coef_t
38 use time_state, only : time_state_t
40 use logger, only : neko_log, neko_log_debug
42 use math, only : masked_gather_copy
44 use comm, only : pe_rank, neko_comm
45 use mpi_f08, only : mpi_allreduce, mpi_in_place, mpi_sum, mpi_integer
46 use utils, only : neko_warning, neko_error
48 use json_module, only : json_file
50 use case, only : case_t
51 use point_zone, only : point_zone_t
55 use dofmap, only : dofmap_t
56 use space, only : space_t, gll
57 use mesh, only : mesh_t
58 use field_list, only : field_list_t
62 use math, only : glsum
63
64 use, intrinsic :: iso_c_binding
65 implicit none
66 private
67
72
75 procedure(compute_intrf), pass(this), pointer :: compute_impl => &
77
79 character(len=80), allocatable :: field_names(:)
81 integer :: n_fields = 0
83 type(field_list_t) :: fields
85 type(field_list_t) :: source_fields
87 type(field_writer_t) :: writer
89 type(dofmap_t) :: dof
90
91 ! =====================================================================
92 ! Variables for point zone masking.
93
95 class(point_zone_t), pointer :: point_zone => null()
98 ! Otherwise, it will point to its own internal masked mesh_t.
99 type(mesh_t), pointer :: msh => null()
101 logical :: internal_mesh = .false.
102
103 ! =====================================================================
104 ! Variables for space-to-space interpolation
105
107 integer :: lx = -1
109 type(interpolator_t) :: interpolator
113 type(space_t), pointer :: xh => null()
117 logical :: internal_space = .false.
118
121 logical :: checked = .false.
122
123 contains
125 procedure, pass(this) :: init => field_subsampler_init_json
127 procedure, pass(this) :: init_common => &
130 procedure, pass(this) :: free => field_subsampler_free
132 procedure, pass(this) :: compute_ => compute_wrapper
134 generic :: init_from_components => &
135 init_from_controllers, init_from_controllers_properties
137 procedure, pass(this) :: init_from_controllers => &
141 procedure, pass(this) :: init_from_controllers_properties => &
144 procedure, pass(this) :: check => field_subsampler_check
145 end type field_subsampler_t
146
149 abstract interface
150 subroutine compute_intrf(this, time)
152 class(field_subsampler_t), intent(inout) :: this
153 type(time_state_t), intent(in) :: time
154 end subroutine compute_intrf
155 end interface
156
157contains
158
160 subroutine field_subsampler_check(this)
161 class(field_subsampler_t), intent(inout) :: this
162
163 integer :: n, ierr
164
165 logical :: is_valid
166 is_valid = .false.
167
168 ! Internal mesh means we are doing point zone masking
169 if (this%internal_mesh) then
170
171 ! Check if point zone exists and mask is set up
172 is_valid = (associated(this%point_zone) .and. &
173 this%point_zone%mask%is_set())
174 if (.not. is_valid) call neko_error("The point zone is missing " // &
175 " or has not been set up properly")
176
177 n = this%point_zone%size
178 call mpi_allreduce(mpi_in_place, n, 1, mpi_integer, mpi_sum, &
179 neko_comm, ierr)
180
181 is_valid = n .gt. 0
182 if (.not. is_valid) call neko_warning("Point zone is empty")
183
184 end if
185
186 ! Internal space means we are doing space-to-space interpolation
187 if (this%internal_space) then
188 is_valid = this%lx .gt. 0
189 if (.not. is_valid) call neko_error("lx has not been set up properly")
190
191 is_valid = allocated(this%interpolator%Xh_to_Yh)
192 if (.not. is_valid) call neko_error("The interpolator has not been " // &
193 "initialized properly")
194 end if
195
196 ! Check the dofmap
197 is_valid = (this%dof%global_size() .gt. 0 .and. allocated(this%dof%x))
198 if (.not. is_valid) call neko_error("Dofmap not initialized or empty")
199
200 ! Check the internal field list, pointing to the fields in the registry
201 ! Technically the size of the fields should be checked but since they are
202 ! all based on this%dofmap we assume if we get here we are fine.
203 is_valid = (this%fields%size() .gt. 0 .and. allocated(this%fields%items))
204 if (.not. is_valid) call neko_error("Internal field_list not initialized")
205
206 ! Lastly, check that we have properly associated compute_impl
207 is_valid = associated(this%compute_impl)
208 if (.not. is_valid) call neko_error("compute_impl not associated")
209
210 this%checked = .true.
211
212 end subroutine field_subsampler_check
213
216 subroutine compute_wrapper(this, time)
217 class(field_subsampler_t), intent(inout) :: this
218 type(time_state_t), intent(in) :: time
219
220 if (.not. this%checked) call this%check()
221 call this%compute_impl(time)
222
223 end subroutine compute_wrapper
224
226 subroutine dummy_compute(this, time)
227 class(field_subsampler_t), intent(inout) :: this
228 type(time_state_t), intent(in) :: time
229
230 call neko_error("field_subsampler must be initialized first!")
231
232 end subroutine dummy_compute
233
235 subroutine field_subsampler_init_json(this, json, case)
236 class(field_subsampler_t), intent(inout), target :: this
237 type(json_file), intent(inout) :: json
238 class(case_t), intent(inout), target :: case
239
240 character(len=:), allocatable :: name
241 character(len=80), allocatable :: which_fields(:)
242 integer :: lx
243
244 logical :: do_space_interp, do_point_zone_masking
245
246 character(len=:), allocatable :: pz_name
247 class(point_zone_t), pointer :: point_zone
248 point_zone => null()
249
250 call this%init_base(json, case)
251
252 call json_get_or_default(json, "name", name, "field_subsampler")
253 call json_get(json, "source_fields", which_fields)
254
255 ! ========================================================================
256 ! Check if the user has provided a polynomial order for space-to-space
257 ! interpolation and initialize accordingly
258 do_space_interp = json%valid_path('polynomial_order')
259 if (do_space_interp) then
260 call json_get_or_default(json, "polynomial_order", lx, &
261 case%fluid%Xh%lx - 1)
262 lx = lx + 1
263 end if
264
265 ! ========================================================================
266 ! Check if the user has provided a point zone for masking and initialize
267 ! accordingly
268 do_point_zone_masking = json%valid_path('point_zone')
269 if (do_point_zone_masking) then
270 call json_get(json, "point_zone", pz_name)
271 point_zone => neko_point_zone_registry%get_point_zone(trim(pz_name))
272 end if
273
274 ! ========================================================================
275 ! Call the common constructor with the appropriate optional arguments
276 if (do_space_interp .and. do_point_zone_masking) then
277 call field_subsampler_init_common(this, name, which_fields, &
278 lx = lx, point_zone = point_zone)
279 else if (do_space_interp .and. .not. do_point_zone_masking) then
280 call field_subsampler_init_common(this, name, which_fields, &
281 lx = lx)
282 else if (.not. do_space_interp .and. do_point_zone_masking) then
283 call field_subsampler_init_common(this, name, which_fields, &
285 else
286 call neko_error("Invalid configuration: please pass either " // &
287 "a point zone or a valid polynomial order.")
288 end if
289
290 ! =======================================================================
291 ! Initialize the field writer based on the new subsampled fields!
292 ! We cannot use the JSON because "fields" is already taken by the field
293
294 block
295 character(len=1024) :: new_field_names(this%n_fields)
296 integer :: i
297
298 do i = 1, this%n_fields
299 new_field_names(i) = trim(this%fields%name(i))
300 end do
301
302 ! Will be picked up by the field_writer.
303 call json%add("fields", new_field_names)
304
305 ! This is needed so the field_writer doesn't pick up the point zone!
306 if (json%valid_path("point_zone")) call json%remove("point_zone")
307
308 ! Force a different file name, as we cannot add these subsampled
309 ! fields to the regular fluid output (different # of elements and
310 ! polynomial order not supported)
311 if (.not. json%valid_path("output_filename")) then
312 call json%add("output_filename", this%name)
313 end if
314
315 call this%writer%init(json, case)
316
317 ! Put the point zone back to have the JSON back as it was :)
318 if (associated(this%point_zone)) call json%add("point_zone", &
319 trim(this%point_zone%name))
320
321 end block
322
323 end subroutine field_subsampler_init_json
324
330 subroutine field_subsampler_init_common(this, name, which_fields, &
331 lx, point_zone)
332 class(field_subsampler_t), intent(inout) :: this
333 character(len=*), intent(in) :: name
334 character(len=*), intent(in) :: which_fields(:)
335 integer, intent(in), optional :: lx
336 class(point_zone_t), pointer, intent(in), optional :: point_zone
337
338 this%name = name
339 this%field_names = which_fields
340 this%n_fields = size(which_fields)
341
342 ! ========================================================================
343 ! Check inputs
344
345 if (.not. present(lx) .and. .not. present(point_zone)) then
346 call neko_error("Invalid configuration: please pass either " // &
347 "a point zone or a polynomial order.")
348 end if
349
350 ! If there is an lx and no point zone, it must be different from the
351 ! simulation's lx
352 if (present(lx)) then
353 if (lx .eq. this%case%fluid%Xh%lx .and. .not. present(point_zone)) then
354 call neko_error("Invalid configuration: no change in " // &
355 "polynomial order and no point zone provided.")
356 end if
357 end if
358
359 ! ========================================================================
360 ! Polynomial order / space interpolation
361
362 this%internal_space = .false.
363 if (present(lx)) then
364 this%lx = lx
365
366 if (this%lx .eq. this%case%fluid%Xh%lx) then
367 call neko_warning("No change in polynomial order " // &
368 "Space-to-space interpolation disabled.")
369 else
370
371 ! Create the subsampled space, with the (strong) assumption that
372 ! lx = ly = lz.
373 allocate(this%Xh)
374 call this%Xh%init(gll, this%lx, this%lx, this%lx)
375 this%internal_space = .true.
376
377 call this%interpolator%init(this%Xh, this%case%fluid%Xh)
378 end if
379
380 end if
381
382 ! If we haven't initialized our own space above, retrieve it from the
383 ! fluid.
384 if (.not. this%internal_space) then
385 this%lx = this%case%fluid%Xh%lx
386 this%Xh => this%case%fluid%Xh
387 end if
388
389 ! ========================================================================
390 ! Point zone masking
391
392 if (present(point_zone)) then
393 this%point_zone => point_zone
394
395 if (.not. point_zone%full_elements) call neko_error("full_elements" // &
396 "must be enabled when sampling a point zone")
397
398 ! Create the subsampled mesh
399 allocate(this%msh)
400 call this%case%fluid%msh%subset_by_mask(this%msh, &
401 point_zone%mask, &
402 this%case%fluid%Xh%lx, & ! Yes, lx, ly and lz should come from the
403 this%case%fluid%Xh%ly, & ! fluid object, since the point_zone
404 this%case%fluid%Xh%lz) ! mask is built from those values.
405 this%internal_mesh = .true.
406
407 else
408 this%msh => this%case%fluid%msh
409 this%internal_mesh = .false.
410 end if
411
412 ! Create the subsampled dofmap
413 call this%dof%init(this%msh, this%Xh)
414
415 ! =======================================================================
416 ! Register the new fields in the registry and initialize the field_lists
417
418 block
419 integer :: i
420 character(len=2048) :: field_name
421
422 call this%fields%init(this%n_fields)
423 call this%source_fields%init(this%n_fields)
424
425 do i = 1, this%n_fields
426
427 ! Point the new, subsampled fields to the registry
428 field_name = this%name // "/" // this%field_names(i)
429 call neko_registry%add_field(this%dof, field_name)
430 this%fields%items(i)%ptr => neko_registry%get_field(trim(field_name))
431
432 ! Point the source fields from the registry to avoid searches
433 this%source_fields%items(i)%ptr => &
434 neko_registry%get_field(this%field_names(i))
435 end do
436 end block
437
438 !========================================================================
439 ! Assign the correct compute() depending on whether interpolation is
440 ! enabled or not and if a point zone is provided or not.
441
442 if (this%internal_space .and. .not. this%internal_mesh) then
443 this%compute_impl => field_subsampler_compute_xh
444 else if (.not. this%internal_space .and. this%internal_mesh) then
445 this%compute_impl => field_subsampler_compute_pz
446 else if (this%internal_space .and. this%internal_mesh) then
447 this%compute_impl => field_subsampler_compute_pz_xh
448 else
449 call neko_error("Invalid configuration")
450 end if
451
452 end subroutine field_subsampler_init_common
453
455 subroutine field_subsampler_free(this)
456 class(field_subsampler_t), intent(inout) :: this
457
458 this%lx = -1
459 this%n_fields = 0
460
461 if (allocated(this%field_names)) deallocate(this%field_names)
462 nullify(this%point_zone)
463
464 if (this%internal_space) then
465 call this%Xh%free()
466 deallocate(this%Xh)
467 this%internal_space = .false.
468 end if
469
470 if (this%internal_mesh) then
471 call this%msh%free()
472 deallocate(this%msh)
473 this%internal_mesh = .false.
474 end if
475
476 call this%dof%free()
477 call this%interpolator%free()
478
479 nullify(this%Xh)
480 nullify(this%msh)
481
482 call this%source_fields%free()
483 call this%fields%free()
484 call this%writer%free()
485
486 call this%free_base()
487
488 this%compute_impl => dummy_compute
489
490 end subroutine field_subsampler_free
491
494 subroutine field_subsampler_compute_pz(this, time)
495 class(field_subsampler_t), intent(inout) :: this
496 type(time_state_t), intent(in) :: time
497
498 integer :: i, n, n_mask
499
500 n = this%source_fields%items(1)%ptr%dof%size()
501 n_mask = this%point_zone%mask%size()
502
503 do i = 1, this%n_fields
504
505 if (neko_bcknd_device .eq. 1) then
506 call device_masked_gather_copy_aligned(this%fields%x_d(i), &
507 this%source_fields%x_d(i), &
508 this%point_zone%mask%get_d(), n, n_mask)
509 else
510 call masked_gather_copy(this%fields%x(i), &
511 this%source_fields%x(i), &
512 this%point_zone%mask%get(), n, n_mask)
513 end if
514
515 end do
516
517 end subroutine field_subsampler_compute_pz
518
521 subroutine field_subsampler_compute_xh(this, time)
522 class(field_subsampler_t), intent(inout) :: this
523 type(time_state_t), intent(in) :: time
524
525 integer :: i
526
527 do i = 1, this%n_fields
528 call this%interpolator%map(this%fields%x(i), &
529 this%source_fields%x(i), &
530 this%msh%nelv, this%Xh)
531 end do
532
533 end subroutine field_subsampler_compute_xh
534
539 subroutine field_subsampler_compute_pz_xh(this, time)
540 class(field_subsampler_t), intent(inout) :: this
541 type(time_state_t), intent(in) :: time
542
543 type(field_t), pointer :: wk
544 integer :: i, n, n_mask, tmp_index
545
546 n = this%source_fields%items(1)%ptr%dof%size()
547 n_mask = this%point_zone%mask%size()
548
549 call neko_scratch_registry%request_field(wk, tmp_index, .false.)
550
551 do i = 1, this%n_fields
552
553 ! First, do a masked copy onto the submesh but that has the same poly.
554 ! order
555 if (neko_bcknd_device .eq. 1) then
556 call device_masked_gather_copy_aligned(wk%x_d, &
557 this%source_fields%x_d(i), &
558 this%point_zone%mask%get_d(), n, n_mask)
559 else
560 call masked_gather_copy(wk%x, &
561 this%source_fields%x(i), &
562 this%point_zone%mask%get(), n, n_mask)
563 end if
564
565 ! Then, map the two different spaces
566 call this%interpolator%map(this%fields%x(i), wk%x, &
567 this%msh%nelv, this%Xh)
568
569 end do
570
571 call neko_scratch_registry%relinquish_field(tmp_index)
572
573 end subroutine field_subsampler_compute_pz_xh
574
586 subroutine field_subsampler_init_from_controllers(this, name, case, order, &
587 preprocess_controller, compute_controller, output_controller, &
588 which_fields, lx, point_zone)
589 class(field_subsampler_t), intent(inout) :: this
590 character(len=*), intent(in) :: name
591 class(case_t), intent(inout), target :: case
592 integer :: order
593 type(time_based_controller_t), intent(in) :: preprocess_controller
594 type(time_based_controller_t), intent(in) :: compute_controller
595 type(time_based_controller_t), intent(in) :: output_controller
596 character(len=*), intent(in) :: which_fields(:)
597 integer, intent(in) :: lx
598 class(point_zone_t), intent(in), pointer, optional :: point_zone
599
600 call this%init_base_from_components(case, order, preprocess_controller, &
601 compute_controller, output_controller)
602
603 call this%init_common(name, which_fields, lx = lx, &
605
607
624 case, order, preprocess_control, preprocess_value, compute_control, &
625 compute_value, output_control, output_value, which_fields, lx, &
626 point_zone)
627 class(field_subsampler_t), intent(inout) :: this
628 character(len=*), intent(in) :: name
629 class(case_t), intent(inout), target :: case
630 integer :: order
631 character(len=*), intent(in) :: preprocess_control
632 real(kind=rp), intent(in) :: preprocess_value
633 character(len=*), intent(in) :: compute_control
634 real(kind=rp), intent(in) :: compute_value
635 character(len=*), intent(in) :: output_control
636 real(kind=rp), intent(in) :: output_value
637 character(len=*), intent(in) :: which_fields(:)
638 integer, intent(in), optional :: lx
639 class(point_zone_t), pointer, intent(in), optional :: point_zone
640
641 call this%init_base_from_components(case, order, preprocess_control, &
642 preprocess_value, compute_control, compute_value, output_control, &
643 output_value)
644
645 call this%init_common(name, which_fields, lx = lx, &
647
649
650end module field_subsampler
Copy data between host and device (or device and device)
Definition device.F90:71
Abstract interface for the compute() subroutine, which will be assigned at runtime depending on the s...
Retrieves a parameter by name or assigns a provided default value. In the latter case also adds the m...
Retrieves a parameter by name or throws an error.
Defines a simulation case.
Definition case.f90:34
Coefficients.
Definition coef.f90:34
Definition comm.F90:1
integer, public pe_rank
MPI rank.
Definition comm.F90:56
type(mpi_comm), public neko_comm
MPI communicator.
Definition comm.F90:43
subroutine, public device_masked_gather_copy_aligned(a_d, b_d, mask_d, n, n_mask, strm)
Gather a masked vector .
Device abstraction, common interface for various accelerators.
Definition device.F90:34
integer, parameter, public host_to_device
Definition device.F90:47
integer, parameter, public device_to_host
Definition device.F90:47
Defines a mapping of the degrees of freedom.
Definition dofmap.f90:35
Implements type field_subsampler_t.
subroutine dummy_compute(this, time)
Dummy subroutine assigned by default to compute_impl.
subroutine field_subsampler_compute_pz(this, time)
Subsample the fields based only on a point zone, no space-to-space interpolation.
subroutine field_subsampler_compute_pz_xh(this, time)
Subsample the fields based a point zone, and with space-to-space interpolation enabled.
subroutine field_subsampler_init_from_controllers(this, name, case, order, preprocess_controller, compute_controller, output_controller, which_fields, lx, point_zone)
Constructor from components, passing controllers.
subroutine field_subsampler_compute_xh(this, time)
Subsample fields wihout any point zone, only space-to-space interpolation is enabled.
subroutine field_subsampler_init_common(this, name, which_fields, lx, point_zone)
Actual constructor.
subroutine compute_wrapper(this, time)
Wrapper for the run-time-assigned subroutine compute_impl, which will be assigned at runtime to the c...
subroutine field_subsampler_init_json(this, json, case)
Constructor.
subroutine field_subsampler_free(this)
Destructor.
subroutine field_subsampler_init_from_controllers_properties(this, name, case, order, preprocess_control, preprocess_value, compute_control, compute_value, output_control, output_value, which_fields, lx, point_zone)
Constructor from components, passing properties to the time_based_controller` components in the base ...
subroutine field_subsampler_check(this)
Checks the validity of the setup before calling compute()
Implements the field_writer_t type.
Defines a field.
Definition field.f90:34
Routines to interpolate between different spaces.
Utilities for retrieving parameters from the case files.
Logging routines.
Definition log.f90:34
integer, parameter, public neko_log_debug
Debug log level.
Definition log.f90:89
type(log_t), public neko_log
Global log stream.
Definition log.f90:79
Definition math.f90:60
subroutine, public masked_gather_copy(a, b, mask, n, n_mask)
Gather a masked vector to reduced contigous vector .
Definition math.f90:371
real(kind=rp) function, public glsum(a, n)
Sum a vector of length n.
Definition math.f90:549
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:12
Implements output_controller_t
type(point_zone_registry_t), target, public neko_point_zone_registry
Global point_zone registry.
Defines a registry for storing solution fields.
Definition registry.f90:34
type(registry_t), target, public neko_registry
Global field registry.
Definition registry.f90:144
Defines a registry for storing and requesting temporary objects This can be used when you have a func...
type(scratch_registry_t), target, public neko_scratch_registry
Global scratch registry.
Simulation components are objects that encapsulate functionality that can be fit to a particular comp...
subroutine compute_(this, time)
Dummy compute function.
Defines a function space.
Definition space.f90:34
integer, parameter, public gll
Definition space.f90:49
Contains the time_based_controller_t type.
Module with things related to the simulation time.
Utilities.
Definition utils.f90:35
subroutine, public neko_warning(warning_msg)
Reports a warning to standard output.
Definition utils.f90:392
Coefficients defined on a given (mesh, ) tuple. Arrays use indices (i,j,k,e): element e,...
Definition coef.f90:63
field_list_t, To be able to group fields together
Implements the field_subsampler_t simulation components, which allows for masking regions of the doma...
A simulation component that writes a 3d field to a file.
Interpolation between two space::space_t.
Base abstract type for point zones.
Base abstract class for simulation components.
The function space for the SEM solution fields.
Definition space.f90:63
A utility type for determining whether an action should be executed based on the current time value....
A struct that contains all info about the time, expand as needed.