Neko 1.99.9
A portable framework for high-order spectral element flow simulations
Loading...
Searching...
No Matches
lpt.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!
34module lpt
35 use num_types, only : rp
36 use json_module, only : json_file
37 use registry, only : neko_registry
38 use field, only : field_t
39 use case, only : case_t
40 use mesh, only : mesh_t
41 use dofmap, only : dofmap_t
42 use coefs, only : coef_t
45 use time_state, only : time_state_t
48 use logger, only : neko_log, log_size
49 use utils, only : neko_error
50 use file, only : file_t
51 use matrix, only : matrix_t
60 use lpt_output, only : lpt_output_t
61 use comm, only : pe_rank
62 use csv_file, only : csv_file_t
63 use vector, only : vector_t
64 use particles, only : particles_t
65 use device, only : device_to_host
68 implicit none
69 private
70
72 type, public :: lpt_t
74 character(:), allocatable :: name
75 ! Fields based on the fluid solution space
76 type(field_t), pointer :: u_field => null()
77 type(field_t), pointer :: v_field => null()
78 type(field_t), pointer :: w_field => null()
79 type(field_t), pointer :: mu_fluid => null()
80 type(field_t), pointer :: rho_fluid => null()
81 type(mesh_t), pointer :: msh => null()
82 type(dofmap_t), pointer :: dm_xh => null()
83 type(coef_t), pointer :: coef => null()
84 integer :: time_order, lag_len
85 integer :: history_len = 0
86 logical :: inertia = .false.
87 real(kind=rp) :: nonlinear_coefficient, nonlinear_exponent
88 logical :: elastic_wall_enabled = .false.
89 integer, allocatable :: wall_zone_indices(:)
90 logical, allocatable :: wall_facet_mask(:, :)
91 type(time_state_t) :: lpt_time
92 logical :: lpt_time_initialized = .false.
93 type(global_interpolation_t) :: global_interp
94 type(lpt_periodic_bc_t) :: periodic_bc
95 type(lpt_migrate_t) :: migration
99 logical :: output_enabled = .false.
100 logical :: log = .true.
101 real(kind=rp) :: start_time = -huge(0.0_rp)
102 contains
103 procedure, pass(this) :: init => lpt_init_from_json
104 procedure, pass(this) :: free => lpt_free
105 procedure, pass(this) :: preprocess => lpt_preprocess
106 procedure, pass(this) :: compute => lpt_compute
107 procedure, private, pass(this) :: read_particles_json
108 procedure, private, pass(this) :: read_particles_csv
109 procedure, private, pass(this) :: evaluate_velocity
110 procedure, private, pass(this) :: evaluate_acceleration
111 procedure, private, pass(this) :: sync_time_controller
112 procedure, private, pass(this) :: ode_integrate_ab_3c
113 procedure, private, pass(this) :: update_current_rhs
114 procedure, private, pass(this) :: write_output
115 procedure, private, pass(this) :: log_status
116 end type lpt_t
117 private :: update_lags
118
119 interface
120
121 module subroutine lpt_init_wall_facet_mask(wall_facet_mask, msh, &
122 wall_zone_indices)
123 logical, allocatable, intent(inout) :: wall_facet_mask(:, :)
124 type(mesh_t), intent(in) :: msh
125 integer, intent(in) :: wall_zone_indices(:)
126 end subroutine lpt_init_wall_facet_mask
127
129 module subroutine lpt_handle_elastic_wall_collisions(this, x_old, y_old, &
130 z_old, u_old, v_old, w_old)
131 class(lpt_t), intent(inout) :: this
132 type(vector_t), intent(in) :: x_old, y_old, z_old
133 type(vector_t), intent(inout) :: u_old, v_old, w_old
134 end subroutine lpt_handle_elastic_wall_collisions
135 end interface
136
137contains
138
142 subroutine lpt_init_from_json(this, json, case)
143 class(lpt_t), intent(inout), target :: this
144 type(json_file), intent(inout) :: json
145 class(case_t), intent(inout), target :: case
146 type(json_file) :: interp_subdict
147 character(len=:), allocatable :: name
148 character(len=:), allocatable :: migration_strategy
149 character(len=:), allocatable :: output_filename
150 character(len=:), allocatable :: output_format
151 character(len=:), allocatable :: snapshots_per_file_str
152 character(len=:), allocatable :: output_path
153 integer :: migration_strategy_id
154 integer :: snapshots_per_file
155 integer :: snapshots_per_file_type
156 logical :: snapshots_per_file_found
157
158 call this%free()
159
160 call json_get_or_default(json, "name", name, "lpt")
161 call json_get_or_default(json, "log", this%log, .true.)
162 call json_get_or_default(json, "start_time", this%start_time, &
163 -huge(0.0_rp))
164
165 this%name = name
166 this%time_order = case%fluid%ext_bdf%advection_time_order
167 this%msh => case%fluid%msh
168 this%dm_Xh => case%fluid%dm_Xh
169 this%coef => case%fluid%c_Xh
170
171 this%lag_len = this%time_order - 1
172 call json_get_or_default(json, "migration_strategy", migration_strategy, &
173 "owner")
174 select case (trim(migration_strategy))
175 case ("owner")
176 migration_strategy_id = lpt_migrate_to_owner
177 case ("none")
178 migration_strategy_id = lpt_migrate_none
179 case default
180 call neko_error("lpt migration_strategy must be 'owner' or 'none'")
181 end select
182 call this%migration%init(this%lag_len, migration_strategy_id)
183
184 call json_get(json, "inertia", this%inertia)
185
186 if (this%inertia) then
187 call json_get_or_default(json, "nonlinear_coefficient", &
188 this%nonlinear_coefficient, 0.15_rp)
189 call json_get_or_default(json, "nonlinear_exponent", &
190 this%nonlinear_exponent, 0.687_rp)
191 this%mu_fluid => neko_registry%get_field_by_name( &
192 case%fluid%name // "_mu")
193 this%rho_fluid => neko_registry%get_field_by_name( &
194 case%fluid%name // "_rho")
195 end if
196 this%u_field => neko_registry%get_field_by_name("u")
197 this%v_field => neko_registry%get_field_by_name("v")
198 this%w_field => neko_registry%get_field_by_name("w")
199
200 if (case%params%valid_path("case.fluid.wall_zone_indices")) then
201 call json_get(case%params, "case.fluid.wall_zone_indices", &
202 this%wall_zone_indices)
203 if (.not. this%inertia .and. size(this%wall_zone_indices) .gt. 0) then
204 call neko_error("lpt wall_zone_indices requires inertia = true")
205 end if
206 this%elastic_wall_enabled = size(this%wall_zone_indices) .gt. 0
207 if (this%elastic_wall_enabled .and. &
208 migration_strategy_id .eq. lpt_migrate_none) then
209 call neko_error("lpt migration_strategy = none is not " // &
210 "compatible with elastic wall collisions")
211 end if
212 if (this%elastic_wall_enabled) then
213 call lpt_init_wall_facet_mask(this%wall_facet_mask, this%msh, &
214 this%wall_zone_indices)
215 end if
216 end if
217
218 call this%read_particles_json(json)
219 call this%migration%initialize_particle_distribution(this%inertia, &
220 this%particles)
221
222 call json_get_subdict_or_empty(json, "interpolation", interp_subdict)
223 call this%global_interp%init(case%fluid%dm_Xh, &
224 params_subdict = interp_subdict)
225 call this%periodic_bc%init(case%fluid%msh, case%fluid%dm_Xh, &
226 case%fluid%c_Xh)
227 call this%migration%migrate_particles(this%global_interp, &
228 this%periodic_bc, this%inertia, this%particles)
229 call this%sync_time_controller(case%time)
230 call this%update_current_rhs()
231
232 call json_get_or_default(json, "output_filename", output_filename, &
233 trim(this%name))
234 call json_get_or_default(json, "output_format", output_format, "csv")
235
236 call json%info("snapshots_per_file", found = snapshots_per_file_found, &
237 var_type = snapshots_per_file_type)
238 if (snapshots_per_file_found) then
239 select case (snapshots_per_file_type)
240 case (5)
241 call json_get(json, "snapshots_per_file", snapshots_per_file)
242 if (snapshots_per_file .lt. 1) then
243 call neko_error("lpt snapshots_per_file must be a positive " // &
244 "integer or 'all'")
245 end if
246 case (7)
247 call json_get(json, "snapshots_per_file", snapshots_per_file_str)
248 if (trim(snapshots_per_file_str) .eq. "all") then
249 snapshots_per_file = 0
250 else
251 call neko_error("lpt snapshots_per_file must be a positive " // &
252 "integer or 'all'")
253 end if
254 case default
255 call neko_error("lpt snapshots_per_file must be a positive " // &
256 "integer or 'all'")
257 end select
258 else
259 snapshots_per_file = 0
260 call json%add("snapshots_per_file", "all")
261 end if
262 output_path = case%output_directory // trim(output_filename) // "." // &
263 trim(output_format)
264 call this%output%init(output_path, this%inertia, snapshots_per_file)
265
266 ! output at the initialisation
267 this%output_enabled = .true.
268 call this%write_output(case%time)
269
270 call this%log_status()
271 end subroutine lpt_init_from_json
272
276 subroutine read_particles_json(this, json)
277 class(lpt_t), intent(inout) :: this
278 type(json_file), intent(inout) :: json
279 real(kind=rp), allocatable :: coords(:)
280 real(kind=rp), allocatable :: vels(:)
281 real(kind=rp), allocatable :: diams(:)
282 real(kind=rp), allocatable :: densities(:)
283 real(kind=rp), pointer, dimension(:) :: x, y, z, u, v, w
284 integer :: n_particles, ind(6)
285
286 if (pe_rank .eq. 0) then
287 if (json%valid_path("coordinates")) then
288 call json_get(json, "coordinates", coords)
289 if (mod(size(coords), 3) .ne. 0) then
290 call neko_error("lpt coordinates must contain 3 values per " // &
291 "particle")
292 end if
293 n_particles = size(coords) / 3
294 if (this%inertia) then
295 call json_get(json, "velocities", vels)
296 if (mod(size(vels), 3) .ne. 0) then
297 call neko_error("lpt velocities must contain 3 values per " // &
298 "particle")
299 end if
300 call json_get(json, "diameters", diams)
301 call json_get(json, "densities", densities)
302 if (size(vels) / 3 .ne. n_particles .or. &
303 size(diams) .ne. n_particles .or. &
304 size(densities) .ne. n_particles) then
305 call neko_error("lpt coordinates, velocities, diameters " // &
306 "and densities must describe the same number of " // &
307 "particles")
308 end if
309 else
310 allocate(vels(size(coords)))
311 allocate(diams(n_particles))
312 allocate(densities(n_particles))
313 vels = 0.0_rp
314 diams = 0.0_rp
315 densities = 0.0_rp
316 end if
317 call neko_scratch_registry%request(x, ind(1), n_particles, .false.)
318 call neko_scratch_registry%request(y, ind(2), n_particles, .false.)
319 call neko_scratch_registry%request(z, ind(3), n_particles, .false.)
320 call neko_scratch_registry%request(u, ind(4), n_particles, .false.)
321 call neko_scratch_registry%request(v, ind(5), n_particles, .false.)
322 call neko_scratch_registry%request(w, ind(6), n_particles, .false.)
323 x = coords(1::3)
324 y = coords(2::3)
325 z = coords(3::3)
326 u = vels(1::3)
327 v = vels(2::3)
328 w = vels(3::3)
329 call this%particles%init(x, y, z, this%time_order, u, v, &
330 w, diams, densities)
331 deallocate(coords)
332 deallocate(vels)
333 deallocate(diams)
334 deallocate(densities)
335 call neko_scratch_registry%relinquish(ind)
336
337 else if (json%valid_path("points_file")) then
338 call this%read_particles_csv(json)
339 else
340 call neko_error("lpt requires either coordinates or points_file")
341 end if
342 else
343 return
344 end if
345 end subroutine read_particles_json
346
349 subroutine read_particles_csv(this, json)
350 class(lpt_t), intent(inout) :: this
351 type(json_file), intent(inout) :: json
352 character(len=:), allocatable :: points_file
353 type(file_t) :: file_in
354 type(matrix_t) :: mat_in
355 real(kind=rp), pointer, dimension(:) :: x, y, z, u, v, w
356 real(kind=rp), allocatable :: diams(:)
357 real(kind=rp), allocatable :: densities(:)
358 integer :: n_particles, ind_basic(3), ind_inertia(6)
359
360 if (pe_rank .ne. 0) return
361
362 call json_get(json, "points_file", points_file)
363 call file_in%init(trim(points_file))
364
365 select type (ft => file_in%file_type)
366 type is (csv_file_t)
367 if (this%inertia) then
368 call mat_in%init(ft%count_lines(), 8)
369 call ft%read(mat_in)
370 n_particles = mat_in%get_nrows()
371 call neko_scratch_registry%request(x, ind_inertia(1), n_particles, &
372 .false.)
373 call neko_scratch_registry%request(y, ind_inertia(2), n_particles, &
374 .false.)
375 call neko_scratch_registry%request(z, ind_inertia(3), n_particles, &
376 .false.)
377 call neko_scratch_registry%request(u, ind_inertia(4), n_particles, &
378 .false.)
379 call neko_scratch_registry%request(v, ind_inertia(5), n_particles, &
380 .false.)
381 call neko_scratch_registry%request(w, ind_inertia(6), n_particles, &
382 .false.)
383 x = mat_in%x(:, 1)
384 y = mat_in%x(:, 2)
385 z = mat_in%x(:, 3)
386 u = mat_in%x(:, 4)
387 v = mat_in%x(:, 5)
388 w = mat_in%x(:, 6)
389 diams = mat_in%x(:, 7)
390 densities = mat_in%x(:, 8)
391 call this%particles%init(x, y, z, this%time_order, u, v, &
392 w, diams, densities)
393 deallocate(diams)
394 deallocate(densities)
395 call neko_scratch_registry%relinquish(ind_inertia)
396 else
397 call mat_in%init(ft%count_lines(), 3)
398 call ft%read(mat_in)
399 n_particles = mat_in%get_nrows()
400 call neko_scratch_registry%request(x, ind_basic(1), n_particles, .false.)
401 call neko_scratch_registry%request(y, ind_basic(2), n_particles, .false.)
402 call neko_scratch_registry%request(z, ind_basic(3), n_particles, .false.)
403 x = mat_in%x(:, 1)
404 y = mat_in%x(:, 2)
405 z = mat_in%x(:, 3)
406 call this%particles%init(x, y, z, this%time_order)
407 call neko_scratch_registry%relinquish(ind_basic)
408 end if
409 class default
410 call neko_error("lpt points_file must be a csv file")
411 end select
412 call mat_in%free()
413 call file_in%free()
414 end subroutine read_particles_csv
415
420 subroutine evaluate_velocity(this, u_fluid, v_fluid, w_fluid)
421 class(lpt_t), intent(inout) :: this
422 type(vector_t), intent(inout) :: u_fluid, v_fluid, w_fluid
423 logical :: do_interp_on_host
424
425 if (this%particles%n .eq. 0) return
426
427 do_interp_on_host = .false.
428 call this%global_interp%evaluate(u_fluid%x, this%u_field%x, &
429 do_interp_on_host)
430 call this%global_interp%evaluate(v_fluid%x, this%v_field%x, &
431 do_interp_on_host)
432 call this%global_interp%evaluate(w_fluid%x, this%w_field%x, &
433 do_interp_on_host)
434
435 end subroutine evaluate_velocity
436
444 subroutine evaluate_acceleration(this, acc_x, acc_y, acc_z, &
445 u_fluid, v_fluid, w_fluid)
446 class(lpt_t), intent(inout) :: this
447 type(vector_t), intent(in) :: u_fluid, v_fluid, w_fluid
448 type(vector_t), intent(inout) :: acc_x, acc_y, acc_z
449 type(vector_t), pointer :: tau_p, Re_p, f, rho_fluid_local
450 type(vector_t), pointer :: mu_fluid_local, nu_fluid_local
451 integer :: ind(5)
452
453 integer :: n
454 logical :: do_interp_on_host
455
456 if (this%particles%n .eq. 0) return
457 n = this%particles%n
458
459 ! Request the scratch storage used throughout the computation.
460 call neko_scratch_registry%request(rho_fluid_local, &
461 ind(1), n, .false.)
462 call neko_scratch_registry%request(mu_fluid_local, &
463 ind(2), n, .false.)
464 call neko_scratch_registry%request(tau_p, &
465 ind(3), n, .false.)
466 call neko_scratch_registry%request(re_p, &
467 ind(4), n, .false.)
468 call neko_scratch_registry%request(f, &
469 ind(5), n, .false.)
470
471 ! Compute the local fluid properties and particle time scale.
472 do_interp_on_host = .false.
473 call this%global_interp%evaluate(mu_fluid_local%x, this%mu_fluid%x, &
474 do_interp_on_host)
475 call this%global_interp%evaluate(rho_fluid_local%x, this%rho_fluid%x, &
476 do_interp_on_host)
477
478 ! compute the time scale
479 call vector_cfill(tau_p, 1.0_rp/18.0_rp)
480 call vector_col2(tau_p, this%particles%rho)
481 call vector_invcol2(tau_p, mu_fluid_local)
482 call vector_col2(tau_p, this%particles%d)
483 call vector_col2(tau_p, this%particles%d)
484
485 ! The dynamic viscosity is no longer needed, so reuse its storage for the
486 ! kinematic viscosity.
487 call vector_invcol2(mu_fluid_local, rho_fluid_local)
488 ! reuse tempoerary array but with a different name for clarity
489 nu_fluid_local => mu_fluid_local
490
491 ! Compute the relative velocity and particle Reynolds number.
492 ! now use acc_xyz as work arrays
493 call vector_sub3(acc_x, u_fluid, this%particles%u)
494 call vector_sub3(acc_y, v_fluid, this%particles%v)
495 call vector_sub3(acc_z, w_fluid, this%particles%w)
496 call vector_vdot3(re_p, acc_x, acc_y, acc_z, acc_x, acc_y, acc_z)
497 call vector_sqrt_inplace(re_p)
498 call vector_col2(re_p, this%particles%d)
499 call vector_invcol2(re_p, nu_fluid_local)
500
501 ! Compute the nonlinear drag correction.
502 call vector_power(f, re_p, this%nonlinear_exponent)
503 call vector_cmult(f, this%nonlinear_coefficient)
504 call vector_cadd(f, 1.0_rp)
505
506 ! Assemble the particle acceleration.
507 call vector_col2(acc_x, f)
508 call vector_col2(acc_y, f)
509 call vector_col2(acc_z, f)
510 call vector_invcol2(acc_x, tau_p)
511 call vector_invcol2(acc_y, tau_p)
512 call vector_invcol2(acc_z, tau_p)
513
514 call neko_scratch_registry%relinquish(ind)
515
516 end subroutine evaluate_acceleration
517
519 subroutine update_current_rhs(this)
520 class(lpt_t), intent(inout) :: this
521 type(vector_t), pointer :: u_fluid, v_fluid, w_fluid
522 integer :: ind(3)
523
524 call profiler_start_region('LPT_migrate_interp')
525
526 call this%migration%migrate_particles(this%global_interp, &
527 this%periodic_bc, this%inertia, this%particles)
528
529 call neko_scratch_registry%request(u_fluid, ind(1), &
530 this%particles%n, .false.)
531 call neko_scratch_registry%request(v_fluid, ind(2), &
532 this%particles%n, .false.)
533 call neko_scratch_registry%request(w_fluid, ind(3), &
534 this%particles%n, .false.)
535
536 call this%evaluate_velocity(u_fluid, v_fluid, w_fluid)
537
538 if (this%inertia) then
539 call this%evaluate_acceleration(this%particles%acc_x, &
540 this%particles%acc_y, this%particles%acc_z, &
541 u_fluid, v_fluid, w_fluid)
542 else
543 this%particles%u = u_fluid
544 this%particles%v = v_fluid
545 this%particles%w = w_fluid
546 end if
547
548 call neko_scratch_registry%relinquish(ind)
549
550 call profiler_end_region('LPT_migrate_interp')
551 end subroutine update_current_rhs
552
557 subroutine update_lags(lag, laglag, new_values)
558 type(vector_t), intent(inout) :: lag, laglag
559 type(vector_t), intent(in) :: new_values
560
561 laglag = lag
562 lag = new_values
563
564 end subroutine update_lags
565
568 subroutine lpt_preprocess(this, time)
569 class(lpt_t), intent(inout) :: this
570 type(time_state_t), intent(in) :: time
571 type(vector_t), pointer :: x_old, y_old, z_old, u_old, v_old, w_old
572 integer :: ind(6)
573
574 associate(x => this%particles%x, y => this%particles%y, &
575 z => this%particles%z, u => this%particles%u, &
576 v => this%particles%v, w => this%particles%w, &
577 acc_x => this%particles%acc_x, &
578 acc_y => this%particles%acc_y, &
579 acc_z => this%particles%acc_z, &
580 u_lag => this%particles%u_lag, &
581 v_lag => this%particles%v_lag, &
582 w_lag => this%particles%w_lag, &
583 u_laglag => this%particles%u_laglag, &
584 v_laglag => this%particles%v_laglag, &
585 w_laglag => this%particles%w_laglag, &
586 acc_xlag => this%particles%acc_xlag, &
587 acc_ylag => this%particles%acc_ylag, &
588 acc_zlag => this%particles%acc_zlag, &
589 acc_xlaglag => this%particles%acc_xlaglag, &
590 acc_ylaglag => this%particles%acc_ylaglag, &
591 acc_zlaglag => this%particles%acc_zlaglag, &
592 n => this%particles%n)
593 if (time%t .lt. this%start_time) return
594 call this%sync_time_controller(time)
595 if (abs(this%lpt_time%dt) .le. epsilon(1.0_rp)) return
596
597 call profiler_start_region('LPT_time_integration')
598
599 call neko_scratch_registry%request(x_old, ind(1), n, .false.)
600 call neko_scratch_registry%request(y_old, ind(2), n, .false.)
601 call neko_scratch_registry%request(z_old, ind(3), n, .false.)
602 call neko_scratch_registry%request(u_old, ind(4), n, .false.)
603 call neko_scratch_registry%request(v_old, ind(5), n, .false.)
604 call neko_scratch_registry%request(w_old, ind(6), n, .false.)
605
606 x_old = x
607 y_old = y
608 z_old = z
609 u_old = u
610 v_old = v
611 w_old = w
612
613 ! Advance the particle state from the previously stored RHS.
614 if (this%inertia) then
615 call this%ODE_integrate_ab_3c(u, v, w, acc_x, acc_y, acc_z, &
616 acc_xlag, acc_ylag, acc_zlag, acc_xlaglag, acc_ylaglag, &
617 acc_zlaglag, n)
618 end if
619
620 ! Advance the coordinates using the velocity history available at step
621 ! entry, before the fluid solve refreshes the current RHS.
622 call this%ODE_integrate_ab_3c(x, y, z, u_old, v_old, w_old, &
623 u_lag, v_lag, w_lag, u_laglag, v_laglag, w_laglag, n)
624
625 ! Handle the wall collisions with the pre-step RHS.
626 if (this%inertia .and. this%elastic_wall_enabled) then
627 call lpt_handle_elastic_wall_collisions(this, x_old, y_old, z_old, &
628 u_old, v_old, w_old)
629 end if
630
631 ! Update lag histories for the next Adams-Bashforth step.
632 if (this%lag_len .gt. 0) then
633 call update_lags(u_lag, u_laglag, u_old)
634 call update_lags(v_lag, v_laglag, v_old)
635 call update_lags(w_lag, w_laglag, w_old)
636 if (this%inertia) then
637 call update_lags(acc_xlag, acc_xlaglag, acc_x)
638 call update_lags(acc_ylag, acc_ylaglag, acc_y)
639 call update_lags(acc_zlag, acc_zlaglag, acc_z)
640 end if
641 this%history_len = min(this%history_len + 1, this%lag_len)
642 end if
643
644 call neko_scratch_registry%relinquish(ind)
645
646 end associate
647
648 call profiler_end_region('LPT_time_integration')
649
650 end subroutine lpt_preprocess
651
654 subroutine lpt_compute(this, time)
655 class(lpt_t), intent(inout) :: this
656 type(time_state_t), intent(in) :: time
657
658 if (time%t .lt. this%start_time) return
659
660 call this%update_current_rhs()
661
662 if (this%output_enabled) then
663 if (this%output_controller%check(time)) then
664 call this%write_output(time)
665 call this%output_controller%register_execution(time)
666 end if
667 end if
668 end subroutine lpt_compute
669
672 subroutine sync_time_controller(this, time)
673 class(lpt_t), intent(inout) :: this
674 type(time_state_t), intent(in) :: time
675 real(kind=rp) :: dt_local
676 real(kind=rp) :: t_ref
677 integer :: i
678
679 if (.not. this%lpt_time_initialized) then
680 this%lpt_time = time
681 t_ref = time%t
682 if (this%start_time .gt. time%t) t_ref = this%start_time
683 this%lpt_time%t = t_ref
684 this%lpt_time%tlag = t_ref
685 this%lpt_time%dt = 0.0_rp
686 this%lpt_time%dtlag = 0.0_rp
687 this%lpt_time_initialized = .true.
688 return
689 end if
690
691 dt_local = time%t - this%lpt_time%t
692 if (abs(dt_local) .le. epsilon(1.0_rp)) then
693 this%lpt_time%t = time%t
694 this%lpt_time%tstep = time%tstep
695 this%lpt_time%dt = 0.0_rp
696 return
697 end if
698
699 do i = size(this%lpt_time%dtlag), 2, -1
700 this%lpt_time%dtlag(i) = this%lpt_time%dtlag(i - 1)
701 this%lpt_time%tlag(i) = this%lpt_time%tlag(i - 1)
702 end do
703 this%lpt_time%dtlag(1) = this%lpt_time%dt
704 this%lpt_time%tlag(1) = this%lpt_time%t
705 this%lpt_time%dt = dt_local
706 this%lpt_time%t = time%t
707 this%lpt_time%tstep = time%tstep
708 end subroutine sync_time_controller
709
715 subroutine ode_integrate_ab_3c(this, sol_x, sol_y, sol_z, &
716 rhs_x, rhs_y, rhs_z, rhs_xlag, rhs_ylag, rhs_zlag, &
717 rhs_xlaglag, rhs_ylaglag, rhs_zlaglag, n)
718 class(lpt_t), intent(inout) :: this
719 type(vector_t), intent(inout) :: sol_x, sol_y, sol_z
720 type(vector_t), intent(in) :: rhs_x, rhs_y, rhs_z
721 type(vector_t), intent(in) :: rhs_xlag, rhs_ylag, rhs_zlag
722 type(vector_t), intent(in) :: rhs_xlaglag, rhs_ylaglag
723 type(vector_t), intent(in) :: rhs_zlaglag
724 integer, intent(in) :: n
725 type(ab_time_scheme_t) :: ab_scheme
726 real(kind=rp) :: ab_coeffs(4), dt_history(10)
727 real(kind=rp) :: dtc
728 integer :: i
729 integer :: nadv
730
731 if (n .eq. 0) return
732
733 ! set up AB coefficients based on the history length available
734 nadv = this%time_order
735 nadv = min(nadv, this%history_len + 1)
736
737 dt_history = 0.0_rp
738 dt_history(1) = this%lpt_time%dt
739 dt_history(2) = this%lpt_time%dtlag(1)
740 dt_history(3) = this%lpt_time%dtlag(2)
741 call ab_scheme%compute_coeffs(ab_coeffs, dt_history, nadv)
742
743 ! contribution from the current velocity
744 dtc = this%lpt_time%dt * ab_coeffs(1)
745
746 call vector_add2s2(sol_x, rhs_x, dtc, n)
747 call vector_add2s2(sol_y, rhs_y, dtc, n)
748 call vector_add2s2(sol_z, rhs_z, dtc, n)
749
750 if (nadv .ge. 2) then
751 dtc = this%lpt_time%dt * ab_coeffs(2)
752 call vector_add2s2(sol_x, rhs_xlag, dtc, n)
753 call vector_add2s2(sol_y, rhs_ylag, dtc, n)
754 call vector_add2s2(sol_z, rhs_zlag, dtc, n)
755 end if
756
757 if (nadv .ge. 3) then
758 dtc = this%lpt_time%dt * ab_coeffs(3)
759 call vector_add2s2(sol_x, rhs_xlaglag, dtc, n)
760 call vector_add2s2(sol_y, rhs_ylaglag, dtc, n)
761 call vector_add2s2(sol_z, rhs_zlaglag, dtc, n)
762 end if
763
764 end subroutine ode_integrate_ab_3c
765
768 subroutine write_output(this, time)
769 class(lpt_t), intent(inout) :: this
770 type(time_state_t), intent(in) :: time
771 real(kind=rp), allocatable :: local_data(:,:)
772 integer :: n_local
773 integer :: i
774 integer :: n_data
775
776 n_local = this%particles%n
777
778 call this%particles%device_sync(device_to_host)
779
780 if (this%inertia) then
781 n_data = 11
782 else
783 n_data = 9
784 end if
785 allocate(local_data(n_data, n_local))
786 do i = 1, n_local
787 local_data(1,i) = real(time%tstep, rp)
788 local_data(2,i) = time%t
789 local_data(3,i) = real(this%particles%ids(i), rp)
790 local_data(4,i) = this%particles%x%x(i)
791 local_data(5,i) = this%particles%y%x(i)
792 local_data(6,i) = this%particles%z%x(i)
793 local_data(7,i) = this%particles%u%x(i)
794 local_data(8,i) = this%particles%v%x(i)
795 local_data(9,i) = this%particles%w%x(i)
796 if (this%inertia) then
797 local_data(10,i) = this%particles%d%x(i)
798 local_data(11,i) = this%particles%rho%x(i)
799 end if
800 end do
801
802 call this%output%write(local_data, n_local)
803 deallocate(local_data)
804 end subroutine write_output
805
807 subroutine lpt_free(this)
808 class(lpt_t), intent(inout) :: this
809
810 call this%particles%free()
811 call this%global_interp%free()
812 call this%periodic_bc%free()
813 call this%migration%free()
814 call this%output%free()
815 call this%output_controller%free()
816
817 this%u_field => null()
818 this%v_field => null()
819 this%w_field => null()
820 this%msh => null()
821 this%dm_Xh => null()
822 this%coef => null()
823 if (allocated(this%wall_zone_indices)) deallocate(this%wall_zone_indices)
824 if (allocated(this%wall_facet_mask)) deallocate(this%wall_facet_mask)
825 this%elastic_wall_enabled = .false.
826 this%output_enabled = .false.
827 this%log = .true.
828 this%start_time = -huge(0.0_rp)
829 this%history_len = 0
830 if (allocated(this%name)) deallocate(this%name)
831 call this%lpt_time%reset()
832 this%lpt_time_initialized = .false.
833 end subroutine lpt_free
834
836 subroutine log_status(this)
837 class(lpt_t), intent(in) :: this
838 character(len=LOG_SIZE) :: log_buf
839
840 if (.not. this%log) return
841
842 call neko_log%section("Lagrangian particle tracking")
843 write(log_buf, '(A,A)') "Name: ", trim(this%name)
844 call neko_log%message(log_buf)
845 write(log_buf, '(A,I0)') "Global seeded particles: ", &
846 this%particles%n_global
847 call neko_log%message(log_buf)
848 if (this%periodic_bc%periodic_enabled) then
849 write(log_buf, '(A,I0)') "Periodic wrap directions: ", &
850 this%periodic_bc%n_periodic_dirs
851 call neko_log%message(log_buf)
852 end if
853 if (this%periodic_bc%rotational_periodic_enabled) then
854 write(log_buf, '(A,3(ES13.5,A),ES13.5)') &
855 "Rotational periodic sector: theta_min = ", &
856 this%periodic_bc%rotational_theta_min, ", theta_max = ", &
857 this%periodic_bc%rotational_theta_max, ", theta_len = ", &
858 this%periodic_bc%rotational_theta_len, ""
859 call neko_log%message(log_buf)
860 end if
861 if (this%elastic_wall_enabled) then
862 write(log_buf, '(A,I0)') "Elastic wall zones configured: ", &
863 size(this%wall_zone_indices)
864 call neko_log%message(log_buf)
865 end if
866 write(log_buf, '(A,I0)') "Local particles on rank 0 at init: ", &
867 this%particles%n
868 if (pe_rank .eq. 0) call neko_log%message(log_buf)
869 call neko_log%end_section()
870 end subroutine log_status
871
872end module lpt
double real
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.
Adam-Bashforth scheme for time integration.
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:59
File format for .csv files, used for any read/write operations involving floating point data.
Definition csv_file.f90:35
Device abstraction, common interface for various accelerators.
Definition device.F90:34
integer, parameter, public device_to_host
Definition device.F90:48
Defines a mapping of the degrees of freedom.
Definition dofmap.f90:35
Defines a field.
Definition field.f90:34
Module for file I/O operations.
Definition file.f90:34
Implements global_interpolation given a dofmap.
Utilities for retrieving parameters from the case files.
subroutine, public json_get_subdict_or_empty(json, key, output)
Extract a sub-object from a json object and returns an empty object if the key is missing.
Logging routines.
Definition log.f90:34
type(log_t), public neko_log
Global log stream.
Definition log.f90:91
integer, parameter, public log_size
Definition log.f90:46
Particle redistribution support for LPT.
integer, parameter, public lpt_migrate_to_owner
integer, parameter, public lpt_migrate_none
Output support for Lagrangian particle tracking.
Periodic and cyclic boundary-condition support for LPT.
Implements lpt_t. (Lagrangian Particle Tracking)
Definition lpt.f90:34
subroutine lpt_compute(this, time)
Refresh particle/fluid coupling after the fluid step and emit output.
Definition lpt.f90:655
subroutine lpt_init_from_json(this, json, case)
Build a mask of elastic wall facets from configured mesh zone ids.
Definition lpt.f90:143
subroutine evaluate_velocity(this, u_fluid, v_fluid, w_fluid)
Interpolate the carrier velocity at the local particles.
Definition lpt.f90:421
subroutine lpt_free(this)
Free all LPT-owned state and reset pointers/flags.
Definition lpt.f90:808
subroutine log_status(this)
Emit a setup summary for the configured LPT instance.
Definition lpt.f90:837
subroutine ode_integrate_ab_3c(this, sol_x, sol_y, sol_z, rhs_x, rhs_y, rhs_z, rhs_xlag, rhs_ylag, rhs_zlag, rhs_xlaglag, rhs_ylaglag, rhs_zlaglag, n)
Advance a three-component state with variable-step Adams-Bashforth.
Definition lpt.f90:718
subroutine, private update_lags(lag, laglag, new_values)
Shift one particle history level and store new current values.
Definition lpt.f90:558
subroutine write_output(this, time)
Write one trajectory snapshot.
Definition lpt.f90:769
subroutine read_particles_csv(this, json)
Read particle data from a CSV file and initialise particles on rank 0.
Definition lpt.f90:350
subroutine update_current_rhs(this)
Refresh particle RHS values using the current fluid solution.
Definition lpt.f90:520
subroutine sync_time_controller(this, time)
Build an LPT-local time-step history from the times at which LPT runs.
Definition lpt.f90:673
subroutine read_particles_json(this, json)
Read particle data from JSON and initialise particles on rank 0.
Definition lpt.f90:277
subroutine evaluate_acceleration(this, acc_x, acc_y, acc_z, u_fluid, v_fluid, w_fluid)
Estimate particle acceleration from local carrier-fluid velocity.
Definition lpt.f90:446
subroutine lpt_preprocess(this, time)
Advance particle positions and, for inertial particles, velocities.
Definition lpt.f90:569
Defines a matrix.
Definition matrix.f90:34
Defines a mesh.
Definition mesh.f90:34
integer, parameter, public rp
Global precision used in computations.
Definition num_types.f90:14
Implements output_controller_t
Defines an output.
Definition output.f90:34
Defines a collection of Lagrangian particles.
Definition particles.f90:34
Profiling interface.
Definition profiler.F90:34
subroutine, public profiler_start_region(name, region_id)
Started a named (name) profiler region.
Definition profiler.F90:79
subroutine, public profiler_end_region(name, region_id)
End the most recently started profiler region.
Definition profiler.F90:116
Defines a registry for storing solution fields.
Definition registry.f90:34
type(registry_t), target, public neko_registry
Global field registry.
Definition registry.f90:158
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.
Contains the time_based_controller_t type.
Module with things related to the simulation time.
Utilities.
Definition utils.f90:35
subroutine, public vector_vdot3(dot, u1, u2, u3, v1, v2, v3, n)
Compute a dot product (3-d version) assuming vector components etc.
subroutine, public vector_sub3(a, b, c, n)
Vector subtraction .
subroutine, public vector_power(ap, a, p, n)
Take the power of a vector .
subroutine, public vector_cmult(a, c, n)
Multiplication by constant c .
subroutine, public vector_col2(a, b, n)
Vector multiplication .
subroutine, public vector_cfill(a, c, n)
Set all elements to a constant c .
subroutine, public vector_invcol2(a, b, n)
Vector division .
subroutine, public vector_sqrt_inplace(a, n)
Sqrt a vector .
subroutine, public vector_cadd(a, s, n)
Add a scalar to vector .
subroutine, public vector_add2s2(a, b, c1, n)
Vector addition with scalar multiplication (multiplication on second argument)
subroutine, public vector_cmult2(a, b, c, n)
Multiplication by constant c .
subroutine, public vector_col3(a, b, c, n)
Vector multiplication with 3 vectors .
Defines a vector.
Definition vector.f90:34
Explicit Adam-Bashforth scheme for time integration.
Coefficients defined on a given (mesh, ) tuple. Arrays use indices (i,j,k,e): element e,...
Definition coef.f90:135
A wrapper around a polymorphic generic_file_t that handles its init. This is essentially a factory fo...
Definition file.f90:56
Implements global interpolation for arbitrary points in the domain.
Passive Lagrangian particle tracking.
Definition lpt.f90:72
Particle positions, velocities, properties, and time-history data.
Definition particles.f90:42
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.