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