Neko 1.99.6
A portable framework for high-order spectral element flow simulations
Loading...
Searching...
No Matches
lpt_output.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
37 use file, only : file_t
38 use hdf5_file, only : hdf5_file_t
39 use matrix, only : matrix_t
40 use tensor, only : trsp
42 use mpi_f08, only : mpi_barrier, mpi_gather, mpi_gatherv, mpi_integer
43 implicit none
44 private
45
46 type, public :: lpt_output_t
47 type(file_t) :: output_file
48 character(len=1024) :: output_path = ""
49 logical :: hdf5_output = .false.
50 logical :: inertia = .false.
51 integer :: snapshots_per_file = 0
52 integer :: snapshots_in_file = 0
53 integer :: output_file_index = 1
54 integer :: hdf5_output_count = 0
55 integer :: n_data = 0
56 contains
57 procedure, pass(this) :: init => lpt_output_init
58 procedure, pass(this) :: free => lpt_output_free
59 procedure, pass(this) :: write => lpt_output_write
60 procedure, private, pass(this) :: init_file => lpt_output_init_file
61 procedure, private, pass(this) :: current_path => lpt_output_current_path
62 procedure, private, pass(this) :: init_hdf5 => lpt_output_init_hdf5
63 procedure, private, pass(this) :: write_csv => lpt_output_write_csv
64 procedure, private, pass(this) :: write_hdf5 => lpt_output_write_hdf5
65 end type lpt_output_t
66
67contains
68
73 subroutine lpt_output_init(this, output_path, inertia, snapshots_per_file)
74 class(lpt_output_t), intent(inout) :: this
75 character(len=*), intent(in) :: output_path
76 logical, intent(in) :: inertia
77 integer, intent(in) :: snapshots_per_file
78
79 call this%free()
80 this%output_path = trim(output_path)
81 this%inertia = inertia
82 this%snapshots_per_file = snapshots_per_file
83
84 if (inertia) then
85 this%n_data = 11
86 else
87 this%n_data = 9
88 end if
89
90 call this%init_file()
91 end subroutine lpt_output_init
92
94 subroutine lpt_output_init_file(this)
95 class(lpt_output_t), intent(inout) :: this
96 character(len=1024) :: output_path
97 character(len=80) :: output_suffix
98
99 call this%output_file%free()
100 call this%current_path(output_path)
101
102 call filename_suffix(output_path, output_suffix)
103 select case (trim(output_suffix))
104 case ("csv")
105 if (this%inertia) then
106 call this%output_file%init(output_path, &
107 header = "tstep,time,particle_id,x,y,z,u,v,w,d,rho", &
108 overwrite = .true.)
109 else
110 call this%output_file%init(output_path, &
111 header = "tstep,time,particle_id,x,y,z,u,v,w", &
112 overwrite = .true.)
113 end if
114 this%hdf5_output = .false.
115 case ("h5", "hdf5")
116 this%hdf5_output = .true.
117 call this%init_hdf5(output_path, this%inertia)
118 case default
119 call neko_error("lpt output_filename must end in .csv, .h5, or .hdf5")
120 end select
121 this%snapshots_in_file = 0
122 end subroutine lpt_output_init_file
123
126 subroutine lpt_output_current_path(this, output_path)
127 class(lpt_output_t), intent(in) :: this
128 character(len=*), intent(out) :: output_path
129 character(len=1024) :: path
130 character(len=1024) :: name
131 character(len=1024) :: suffix
132
133
134 call filename_split(trim(this%output_path), path, name, suffix)
135 write(output_path, '(A,A,A,I0,A)') trim(path), trim(name), &
136 "_", this%output_file_index, trim(suffix)
137
138 end subroutine lpt_output_current_path
139
143 subroutine lpt_output_init_hdf5(this, output_path, inertia)
144 class(lpt_output_t), intent(inout) :: this
145 character(len=*), intent(in) :: output_path
146 logical, intent(in) :: inertia
147 integer :: file_unit
148 integer :: ierr
149 integer :: out_int
150
151 ! The granular HDF5 API appends to existing datasets, so ensure this run
152 ! starts with a fresh file before hdf5_file_t creates it collectively.
153 if (pe_rank .eq. 0) then
154 open(newunit = file_unit, file = trim(output_path), status = "replace", &
155 action = "write", iostat = ierr)
156 if (ierr .ne. 0) then
157 call neko_error("Error while creating " // trim(output_path))
158 end if
159 close(file_unit, status = "delete")
160 end if
161 call mpi_barrier(neko_comm, ierr)
162
163 call this%output_file%init(output_path)
164
165 select type (ft => this%output_file%file_type)
166 type is (hdf5_file_t)
167 call ft%set_overwrite(.false.)
168 call ft%open("w")
169 call ft%set_active_group("lpt")
170 out_int = 3
171 call ft%write_attribute("FormatVersion", out_int)
172 out_int = this%n_data
173 call ft%write_attribute("NColumns", out_int)
174 if (inertia) then
175 out_int = 7
176 else
177 out_int = 5
178 end if
179 call ft%write_attribute("NCategories", out_int)
180 if (inertia) then
181 out_int = 1
182 else
183 out_int = 0
184 end if
185 call ft%write_attribute("Inertia", out_int)
186 out_int = 0
187 call ft%write_attribute("NSteps", out_int)
188 call ft%close()
189 class default
190 call neko_error("lpt internal error: expected hdf5_file_t")
191 end select
192
193 this%hdf5_output_count = 0
194 end subroutine lpt_output_init_hdf5
195
199 subroutine lpt_output_write(this, local_data, n_local)
200 class(lpt_output_t), intent(inout) :: this
201 integer, intent(in) :: n_local
202 real(kind=rp), intent(in) :: local_data(this%n_data, n_local)
203
204 if (this%snapshots_per_file .gt. 0 .and. &
205 this%snapshots_in_file .ge. this%snapshots_per_file) then
206 this%output_file_index = this%output_file_index + 1
207 call this%init_file()
208 end if
209
210 if (this%hdf5_output) then
211 call this%write_hdf5(local_data, n_local)
212 else
213 call this%write_csv(local_data, n_local)
214 end if
215 this%snapshots_in_file = this%snapshots_in_file + 1
216 end subroutine lpt_output_write
217
221 subroutine lpt_output_write_hdf5(this, local_data, n_local)
222 class(lpt_output_t), intent(inout) :: this
223 integer, intent(in) :: n_local
224 real(kind=rp), intent(in) :: local_data(this%n_data, n_local)
225 type(matrix_t) :: tsteps
226 type(matrix_t) :: t
227 type(matrix_t) :: ids
228 type(matrix_t) :: position
229 type(matrix_t) :: velocity
230 type(matrix_t) :: diameter
231 type(matrix_t) :: density
232 integer :: out_int
233
234 call tsteps%init(1, n_local, "tsteps")
235 call t%init(1, n_local, "t")
236 call ids%init(1, n_local, "ids")
237 call position%init(3, n_local, "position")
238 call velocity%init(3, n_local, "velocity")
239 if (this%inertia) then
240 call diameter%init(1, n_local, "diameter")
241 call density%init(1, n_local, "density")
242 end if
243
244 if (n_local .gt. 0) then
245 tsteps%x(1, :) = local_data(1, :)
246 t%x(1, :) = local_data(2, :)
247 ids%x(1, :) = local_data(3, :)
248 position%x = local_data(4:6, :)
249 velocity%x = local_data(7:9, :)
250 if (this%inertia) then
251 diameter%x(1, :) = local_data(10, :)
252 density%x(1, :) = local_data(11, :)
253 end if
254 end if
255
256 select type (ft => this%output_file%file_type)
257 type is (hdf5_file_t)
258 call ft%open("w")
259 call ft%set_active_group("lpt")
260 out_int = this%hdf5_output_count + 1
261 call ft%write_attribute("NSteps", out_int)
262 call ft%write_dataset(tsteps)
263 call ft%write_dataset(t)
264 call ft%write_dataset(ids)
265 call ft%write_dataset(position)
266 call ft%write_dataset(velocity)
267 if (this%inertia) then
268 call ft%write_dataset(diameter)
269 call ft%write_dataset(density)
270 end if
271 call ft%close()
272 class default
273 call neko_error("lpt internal error: expected hdf5_file_t")
274 end select
275
276 this%hdf5_output_count = this%hdf5_output_count + 1
277 call tsteps%free()
278 call t%free()
279 call ids%free()
280 call position%free()
281 call velocity%free()
282 if (this%inertia) then
283 call diameter%free()
284 call density%free()
285 end if
286 end subroutine lpt_output_write_hdf5
287
291 subroutine lpt_output_write_csv(this, local_data, n_local)
292 class(lpt_output_t), intent(inout) :: this
293 integer, intent(in) :: n_local
294 real(kind=rp), intent(in) :: local_data(this%n_data, n_local)
295 type(matrix_t) :: block
296 real(kind=rp), allocatable :: global_data(:, :)
297 integer, allocatable :: n_local_particles_per_rank(:)
298 integer, allocatable :: recvcounts(:)
299 integer, allocatable :: displs(:)
300 integer :: total_particles
301 integer :: i
302 integer :: ierr
303
304 if (pe_rank .eq. 0) then
305 allocate(n_local_particles_per_rank(pe_size))
306 else
307 allocate(n_local_particles_per_rank(0))
308 end if
309 call mpi_gather(n_local, 1, mpi_integer, n_local_particles_per_rank, 1, &
310 mpi_integer, 0, neko_comm, ierr)
311
312 if (pe_rank .eq. 0) then
313 allocate(recvcounts(pe_size))
314 allocate(displs(pe_size))
315 recvcounts = 0
316 displs = 0
317
318 total_particles = 0
319 do i = 1, pe_size
320 total_particles = total_particles + n_local_particles_per_rank(i)
321 recvcounts(i) = this%n_data * n_local_particles_per_rank(i)
322 displs(i) = this%n_data * &
323 (total_particles - n_local_particles_per_rank(i))
324 end do
325
326 allocate(global_data(this%n_data, total_particles))
327 else
328 allocate(recvcounts(0))
329 allocate(displs(0))
330 allocate(global_data(this%n_data, 0))
331 end if
332
333 call mpi_gatherv(local_data, this%n_data * n_local, mpi_real_precision, &
334 global_data, recvcounts, displs, mpi_real_precision, 0, &
335 neko_comm, ierr)
336
337 if (pe_rank .eq. 0) then
338 call block%init(total_particles, this%n_data)
339 call trsp(block%x, total_particles, global_data, this%n_data)
340 call this%output_file%write(block)
341 call block%free()
342 end if
343
344 deallocate(global_data)
345 deallocate(n_local_particles_per_rank)
346 deallocate(recvcounts)
347 deallocate(displs)
348 end subroutine lpt_output_write_csv
349
351 subroutine lpt_output_free(this)
352 class(lpt_output_t), intent(inout) :: this
353
354 call this%output_file%free()
355 this%output_path = ""
356 this%hdf5_output = .false.
357 this%inertia = .false.
358 this%snapshots_per_file = 0
359 this%snapshots_in_file = 0
360 this%output_file_index = 0
361 this%hdf5_output_count = 0
362 this%n_data = 0
363 end subroutine lpt_output_free
364
365end module lpt_output
Definition comm.F90:1
type(mpi_datatype), public mpi_real_precision
MPI type for working precision of REAL types.
Definition comm.F90:54
integer, public pe_size
MPI size of communicator.
Definition comm.F90:62
integer, public pe_rank
MPI rank.
Definition comm.F90:59
type(mpi_comm), public neko_comm
MPI communicator.
Definition comm.F90:46
Module for file I/O operations.
Definition file.f90:34
HDF5 file format.
Definition hdf5_file.F90:34
Output support for Lagrangian particle tracking.
subroutine lpt_output_write(this, local_data, n_local)
Write one trajectory snapshot.
subroutine lpt_output_init_hdf5(this, output_path, inertia)
Initialise an HDF5 LPT trajectory file.
subroutine lpt_output_current_path(this, output_path)
Get the active output path, adding a file index when chunked output is on.
subroutine lpt_output_init(this, output_path, inertia, snapshots_per_file)
Initialise the LPT output writer.
subroutine lpt_output_write_csv(this, local_data, n_local)
Write one trajectory snapshot to CSV by gathering particle data to rank 0.
subroutine lpt_output_free(this)
Free the output writer and reset file/output counters.
subroutine lpt_output_init_file(this)
Initialise the currently active LPT output file.
subroutine lpt_output_write_hdf5(this, local_data, n_local)
Append one local LPT trajectory snapshot to an HDF5 file.
Defines a matrix.
Definition matrix.f90:34
integer, parameter, public rp
Global precision used in computations.
Definition num_types.f90:12
Tensor operations.
Definition tensor.f90:61
subroutine, public trsp(a, lda, b, ldb)
Transpose of a rectangular tensor .
Definition tensor.f90:124
Utilities.
Definition utils.f90:35
subroutine, public filename_split(fname, path, name, suffix)
Extract file name components.
Definition utils.f90:131
subroutine, public filename_suffix(fname, suffix)
Extract a filename's suffix.
Definition utils.f90:124
A wrapper around a polymorphic generic_file_t that handles its init. This is essentially a factory fo...
Definition file.f90:56
Interface for HDF5 files.
Definition hdf5_file.F90:60