From 731dd59a77540743db4caa6ffa9f8f0b6c1cb2b3 Mon Sep 17 00:00:00 2001 From: Spencer Bryngelson Date: Sat, 12 Sep 2026 00:27:56 -0400 Subject: [PATCH] Abort when post_process is given a different grid than the restart files post_process checks that the restart files exist but never that they hold the grid the case file asks for. Give it a case whose resolution no longer matches the run and it reads past the end of every file, post-processes the overrun, and exits 0 with NaN-filled output. Nothing reports a problem until someone plots it, or until a diagnostic built on that output starts returning NaN and gets blamed instead. That is what happened here: a 349 M cell run (m = 1644) post-processed with a case file that had since moved to m = 2056, so 681 M cells were read from files holding 349 M. x_cb.dat holds one value per cell boundary, so its size states which grid wrote the restart. The check costs one inquire on a file already being opened, fires before any bulk read, and names both grids. The size is taken from storage_size(0._wp) rather than a literal 8, since x_cb.dat is written with mpi_p and a --single build writes four-byte reals. down_sample reads a full-resolution file with a stride of three, touching stride*(m_glb + 1) + 1 boundaries, so it needs more of the file than m_glb + 2 and only the un-strided read can pin the size exactly -- three source grids of different size can down-sample to the same m_glb. --- src/post_process/m_data_input.f90 | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/post_process/m_data_input.f90 b/src/post_process/m_data_input.f90 index bd228fdbd..e3ee7e840 100644 --- a/src/post_process/m_data_input.f90 +++ b/src/post_process/m_data_input.f90 @@ -276,6 +276,8 @@ impure subroutine s_read_parallel_data_files(t_step) integer(kind=MPI_OFFSET_KIND) :: offset character(LEN=path_len + 2*name_len) :: file_loc logical :: file_exist + integer(kind=8) :: file_bytes, bytes_needed + character(len=10) :: case_m_str, file_m_str character(len=10) :: t_step_string integer :: i @@ -290,9 +292,24 @@ impure subroutine s_read_parallel_data_files(t_step) end if file_loc = trim(case_dir) // '/restart_data' // trim(mpiiofs) // 'x_cb.dat' - inquire (FILE=trim(file_loc), EXIST=file_exist) - + inquire (FILE=trim(file_loc), EXIST=file_exist, SIZE=file_bytes) + + ! The grid file holds one cell boundary per value, so its size says which grid wrote the restart. Without + ! this check a case file whose resolution no longer matches the run reads past the end of every restart + ! file and post-processes silently, exiting 0 with NaN-filled output -- which is indistinguishable from + ! success until someone plots it. The strided read down_sample performs touches stride*(m_glb + 1) + 1 + ! boundaries of a full-resolution file, so it needs more of the file, not less; only the un-strided read + ! pins the size exactly, since down-sampling three grids of different size can land on the same m_glb. if (file_exist) then + bytes_needed = (int(stride, 8)*int(m_glb + 1, 8) + 1_8)*int(storage_size(0._wp)/8, 8) + if (file_bytes < bytes_needed .or. (.not. down_sample .and. file_bytes /= bytes_needed)) then + call s_int_to_str(m_glb, case_m_str) + call s_int_to_str(int(file_bytes/int(storage_size(0._wp)/8, 8)) - 2, file_m_str) + call s_mpi_abort('Restart grid mismatch: this case has m = ' // trim(case_m_str) // ' but ' // trim(file_loc) & + & // ' was written with m = ' // trim(file_m_str) & + & // '. Post-processing must use the same grid as the run that wrote the ' & + & // 'restart files, or it reads past the end of every file and writes NaN.') + end if data_size = m_glb + 2 call MPI_FILE_OPEN(MPI_COMM_WORLD, file_loc, MPI_MODE_RDONLY, mpi_info_int, ifile, ierr)