xref: /honee/src/honee-file.c (revision cde3d787254b08959b09942e5479b240fd3e38f4)
1 // SPDX-FileCopyrightText: Copyright (c) 2017-2024, HONEE contributors.
2 // SPDX-License-Identifier: Apache-2.0 OR BSD-2-Clause
3 
4 /// @file
5 /// Custom file I/O functions for HONEE
6 
7 #include <honee-file.h>
8 
9 /**
10   @brief Check if a filename has a file extension
11 
12   Developer's note: Could instead use PetscStrendswith
13 
14   @param[in]  comm         `MPI_Comm` used for error handling
15   @param[in]  filename     Filename to check
16   @param[in]  extension    Extension to check for
17   @param[out] is_extension Whether the filename has the extension
18 **/
19 PetscErrorCode HoneeCheckFilenameExtension(MPI_Comm comm, const char filename[], const char extension[], PetscBool *is_extension) {
20   size_t len, ext_len;
21 
22   PetscFunctionBeginUser;
23   PetscCall(PetscStrlen(filename, &len));
24   PetscCall(PetscStrlen(extension, &ext_len));
25   PetscCheck(ext_len, comm, PETSC_ERR_ARG_WRONG, "Zero-size extension: %s", extension);
26   if (len < ext_len) *is_extension = PETSC_FALSE;
27   else PetscCall(PetscStrncmp(filename + len - ext_len, extension, ext_len, is_extension));
28   PetscFunctionReturn(PETSC_SUCCESS);
29 }
30 
31 const PetscInt32 HONEE_FILE_TOKEN    = 0xceedf00;  // for backwards compatibility
32 const PetscInt32 HONEE_FILE_TOKEN_32 = 0xceedf32;
33 const PetscInt32 HONEE_FILE_TOKEN_64 = 0xceedf64;
34 
35 // @brief Read in binary int based on it's data type
36 static PetscErrorCode BinaryReadIntoInt(PetscViewer viewer, PetscInt *out, PetscDataType file_type) {
37   PetscFunctionBeginUser;
38   *out = -13;  // appease the overzealous GCC compiler warning Gods
39   if (file_type == PETSC_INT32) {
40     PetscInt32 val;
41     PetscCall(PetscViewerBinaryRead(viewer, &val, 1, NULL, PETSC_INT32));
42     *out = val;
43   } else if (file_type == PETSC_INT64) {
44     PetscInt64 val;
45     PetscCall(PetscViewerBinaryRead(viewer, &val, 1, NULL, PETSC_INT64));
46     PetscCall(PetscIntCast(val, out));
47   } else {
48     PetscCall(PetscViewerBinaryRead(viewer, out, 1, NULL, PETSC_INT));
49   }
50   PetscFunctionReturn(PETSC_SUCCESS);
51 }
52 
53 /**
54   @brief Load initial condition from file
55 
56   @param[in]  filename       File to get data from (must be binary or CGNS file)
57   @param[out] solution_steps Number of timesteps that the initial condition was taken from
58   @param[out] solution_time  Solution time that the initial condition was taken from
59   @param[out] Q              `Vec` to hold the initial condition
60 **/
61 PetscErrorCode HoneeLoadInitialCondition(const char filename[], PetscInt *solution_steps, PetscReal *solution_time, Vec Q) {
62   MPI_Comm    comm;
63   PetscViewer viewer;
64   PetscBool   isBin, isCGNS;
65 
66   PetscFunctionBeginUser;
67   PetscCall(PetscObjectGetComm((PetscObject)Q, &comm));
68   PetscCall(HoneeCheckFilenameExtension(comm, filename, ".bin", &isBin));
69   PetscCall(HoneeCheckFilenameExtension(comm, filename, ".cgns", &isCGNS));
70 
71   if (isBin) {
72     PetscCall(PetscViewerBinaryOpen(comm, filename, FILE_MODE_READ, &viewer));
73     PetscCall(HoneeLoadBinaryVec(viewer, Q, solution_time, solution_steps));
74     PetscCall(PetscViewerDestroy(&viewer));
75   } else if (isCGNS) {
76 #if defined(PETSC_HAVE_CGNS)
77     DM        dm, dm_output;
78     Vec       V_output, V_local;
79     PetscBool set;
80 
81     PetscCall(VecGetDM(Q, &dm));
82     PetscCall(PetscViewerCGNSOpen(comm, filename, FILE_MODE_READ, &viewer));
83     PetscCall(DMGetOutputDM(dm, &dm_output));
84     PetscCall(DMCreateGlobalVector(dm_output, &V_output));
85     PetscCall(DMGetLocalVector(dm, &V_local));
86     PetscCall(VecLoad(V_output, viewer));
87     PetscCall(DMGlobalToLocal(dm_output, V_output, INSERT_VALUES, V_local));
88     PetscCall(DMLocalToGlobal(dm, V_local, INSERT_VALUES, Q));
89     PetscCall(VecDestroy(&V_output));
90     PetscCall(DMRestoreLocalVector(dm, &V_local));
91 
92     PetscCall(PetscViewerCGNSGetSolutionTime(viewer, solution_time, &set));
93     if (!set) PetscCall(PetscPrintf(comm, "WARNING: Couldn't find solution time in file\n"));
94     PetscCall(PetscViewerCGNSGetSolutionIteration(viewer, solution_steps, &set));
95     if (!set) {  // Based on assumption that solution name of has form "FlowSolutionXXX"
96       const char *name, flowsolution[] = "FlowSolution";
97       size_t      flowsolutionlen;
98       PetscBool   isFlowSolution;
99 
100       PetscCall(PetscViewerCGNSGetSolutionName(viewer, &name));
101       PetscCall(PetscStrlen(flowsolution, &flowsolutionlen));
102       PetscCall(PetscStrncmp(flowsolution, name, flowsolutionlen, &isFlowSolution));
103       PetscCheck(isFlowSolution, comm, PETSC_ERR_FILE_UNEXPECTED,
104                  "CGNS file %s does not have FlowIterations array and solution name have 'FlowSolution' (found '%s')", filename, name);
105       *solution_steps = atoi(&name[flowsolutionlen]);
106     }
107 
108     PetscCall(PetscViewerDestroy(&viewer));
109 #else
110     SETERRQ(comm, PETSC_ERR_SUP, "Loading initial condition from CGNS requires PETSc to be built with support. Reconfigure using --with-cgns-dir");
111 #endif
112   } else SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_SUP, "File does not have a valid extension, recieved '%s'", filename);
113   PetscFunctionReturn(PETSC_SUCCESS);
114 }
115 
116 /**
117   @brief Load vector from binary file, possibly with embedded solution time and step number
118 
119   Reads in Vec from binary file, possibly written by HONEE.
120   If written by HONEE, will also load the solution time and timestep, otherwise not.
121   Also handles case where file was written with different PetscInt size than is read with.
122 
123   @param[in]  viewer      `PetscViewer` to read the vec from. Must be a binary viewer
124   @param[out] Q           `Vec` to read the data into
125   @param[out] time        Solution time the Vec was written at, or `NULL`. Set to 0 if legacy file format.
126   @param[out] step_number Timestep number the Vec was written at, or `NULL`. Set to 0 if legacy file format.
127 **/
128 PetscErrorCode HoneeLoadBinaryVec(PetscViewer viewer, Vec Q, PetscReal *time, PetscInt *step_number) {
129   PetscInt    file_step_number;
130   PetscReal   file_time;
131   PetscInt32  token;
132   MPI_Comm    comm = PetscObjectComm((PetscObject)viewer);
133   const char *filename;
134 
135   PetscFunctionBeginUser;
136   PetscCall(PetscViewerFileGetName(viewer, &filename));
137   PetscCall(PetscViewerBinaryRead(viewer, &token, 1, NULL, PETSC_INT32));
138   if (token == HONEE_FILE_TOKEN_32 || token == HONEE_FILE_TOKEN_64 || token == HONEE_FILE_TOKEN) {
139     // HONEE formatted file; we're reading a file with step number and time in the header
140     PetscDataType file_type = PETSC_INT32;
141     if (token == HONEE_FILE_TOKEN_32) file_type = PETSC_INT32;
142     else if (token == HONEE_FILE_TOKEN_64) file_type = PETSC_INT64;
143     PetscCall(BinaryReadIntoInt(viewer, &file_step_number, file_type));
144     PetscCall(PetscViewerBinaryRead(viewer, &file_time, 1, NULL, PETSC_REAL));
145   } else {
146     // Legacy format of just the vector, encoded as [VEC_FILE_CLASSID, length, ]
147     // NOTE: Only used for regression testing now
148     PetscInt length, N;
149 
150     PetscCall(VecGetSize(Q, &N));
151     if (token == VEC_FILE_CLASSID) {
152       PetscCall(BinaryReadIntoInt(viewer, &length, PETSC_INT32));
153     } else {  // See `VecLoad_Binary()` in PETSc for source code reference of how to handle Vecs written with 64-bit PETSc
154       PetscInt32 token2;
155 
156       PetscCall(PetscViewerBinaryRead(viewer, &token2, 1, NULL, PETSC_INT32));
157       token = (PetscInt)((uint64_t)token << 32) + token2;  // Reconstruct token
158       PetscCheck(token == VEC_FILE_CLASSID, comm, PETSC_ERR_FILE_UNEXPECTED, "Not a HONEE header token or a PETSc Vec in file '%s'", filename);
159       PetscCall(BinaryReadIntoInt(viewer, &length, PETSC_INT64));
160     }
161 
162     PetscCheck(length == N, comm, PETSC_ERR_ARG_INCOMP, "File Vec '%s' has length %" PetscInt_FMT " but DM has global Vec size %" PetscInt_FMT,
163                filename, length, N);
164     PetscCall(PetscViewerBinarySetSkipHeader(viewer, PETSC_TRUE));
165     file_time        = 0;
166     file_step_number = 0;
167   }
168 
169   if (time) *time = file_time;
170   if (step_number) *step_number = file_step_number;
171   PetscCall(VecLoad(Q, viewer));
172   PetscFunctionReturn(PETSC_SUCCESS);
173 }
174 
175 /**
176   @brief Write vector to binary file with solution time and step number
177 
178   @param[in] viewer      `PetscViewer` for binary file. Must be binary viewer and in write mode
179   @param[in] Q           `Vec` of the solution
180   @param[in] time        Solution time of the `Vec`
181   @param[in] step_number Timestep number of the Vec
182 **/
183 PetscErrorCode HoneeWriteBinaryVec(PetscViewer viewer, Vec Q, PetscReal time, PetscInt step_number) {
184   PetscInt32 token = PetscDefined(USE_64BIT_INDICES) ? HONEE_FILE_TOKEN_64 : HONEE_FILE_TOKEN_32;
185 
186   PetscFunctionBeginUser;
187   {  // Verify viewer is in correct state
188     PetscViewerType viewer_type;
189     PetscFileMode   file_mode;
190     PetscBool       is_binary_viewer;
191     MPI_Comm        comm = PetscObjectComm((PetscObject)viewer);
192 
193     PetscCall(PetscViewerGetType(viewer, &viewer_type));
194     PetscCall(PetscStrcmp(viewer_type, PETSCVIEWERBINARY, &is_binary_viewer));
195     PetscCheck(is_binary_viewer, comm, PETSC_ERR_ARG_WRONGSTATE, "Viewer must be binary type; instead got %s", viewer_type);
196     PetscCall(PetscViewerFileGetMode(viewer, &file_mode));
197     PetscCheck(file_mode == FILE_MODE_WRITE, comm, PETSC_ERR_ARG_WRONGSTATE, "Viewer must be binary type; instead got %s",
198                file_mode == -1 ? "UNDEFINED" : PetscFileModes[file_mode]);
199   }
200 
201   PetscCall(PetscViewerBinaryWrite(viewer, &token, 1, PETSC_INT32));
202   PetscCall(PetscViewerBinaryWrite(viewer, &step_number, 1, PETSC_INT));
203   PetscCall(PetscViewerBinaryWrite(viewer, &time, 1, PETSC_REAL));
204   PetscCall(VecView(Q, viewer));
205   PetscFunctionReturn(PETSC_SUCCESS);
206 }
207 
208 /**
209   @brief Open a PHASTA *.dat file, grabbing dimensions and file pointer
210 
211   This function opens the file specified by `path` using `PetscFOpen` and passes the file pointer in `fp`.
212   It is not closed in this function, thus `fp` must be closed sometime after this function has been called (using `PetscFClose` for example).
213 
214   Assumes that the first line of the file has the number of rows and columns as the only two entries, separated by a single space.
215 
216   @param[in]  comm           MPI_Comm for the program
217   @param[in]  path           Path to the file
218   @param[in]  char_array_len Length of the character array that should contain each line
219   @param[out] dims           Dimensions of the file, taken from the first line of the file
220   @param[out] fp File        pointer to the opened file
221 **/
222 PetscErrorCode PhastaDatFileOpen(const MPI_Comm comm, const char path[], const PetscInt char_array_len, PetscInt dims[2], FILE **fp) {
223   int    ndims;
224   char   line[char_array_len];
225   char **array;
226 
227   PetscFunctionBeginUser;
228   PetscCall(PetscFOpen(comm, path, "r", fp));
229   PetscCall(PetscSynchronizedFGets(comm, *fp, char_array_len, line));
230   PetscCall(PetscStrToArray(line, ' ', &ndims, &array));
231   PetscCheck(ndims == 2, comm, PETSC_ERR_FILE_UNEXPECTED, "Found %d dimensions instead of 2 on the first line of %s", ndims, path);
232 
233   for (PetscInt i = 0; i < ndims; i++) dims[i] = atoi(array[i]);
234   PetscCall(PetscStrToArrayDestroy(ndims, array));
235   PetscFunctionReturn(PETSC_SUCCESS);
236 }
237 
238 /**
239   @brief Get the number of rows for the PHASTA file at path.
240 
241   Assumes that the first line of the file has the number of rows and columns as the only two entries, separated by a single space.
242 
243   @param[in]  comm  `MPI_Comm` for the program
244   @param[in]  path  Path to the file
245   @param[out] nrows Number of rows
246 **/
247 PetscErrorCode PhastaDatFileGetNRows(const MPI_Comm comm, const char path[], PetscInt *nrows) {
248   const PetscInt char_array_len = 512;
249   PetscInt       dims[2];
250   FILE          *fp;
251 
252   PetscFunctionBeginUser;
253   PetscCall(PhastaDatFileOpen(comm, path, char_array_len, dims, &fp));
254   *nrows = dims[0];
255   PetscCall(PetscFClose(comm, fp));
256   PetscFunctionReturn(PETSC_SUCCESS);
257 }
258 
259 /**
260   @brief Read PetscReal values from PHASTA file
261 
262   @param[in]  comm  `MPI_Comm` for the program
263   @param[in]  path  Path to the file
264   @param[out] array Pointer to allocated array of correct size
265 **/
266 PetscErrorCode PhastaDatFileReadToArrayReal(MPI_Comm comm, const char path[], PetscReal array[]) {
267   PetscInt       dims[2];
268   FILE          *fp;
269   const PetscInt char_array_len = 512;
270   char           line[char_array_len];
271 
272   PetscFunctionBeginUser;
273   PetscCall(PhastaDatFileOpen(comm, path, char_array_len, dims, &fp));
274 
275   for (PetscInt i = 0; i < dims[0]; i++) {
276     int    ndims;
277     char **row_array;
278 
279     PetscCall(PetscSynchronizedFGets(comm, fp, char_array_len, line));
280     PetscCall(PetscStrToArray(line, ' ', &ndims, &row_array));
281     PetscCheck(ndims == dims[1], comm, PETSC_ERR_FILE_UNEXPECTED,
282                "Line %" PetscInt_FMT " of %s does not contain enough columns (%d instead of %" PetscInt_FMT ")", i, path, ndims, dims[1]);
283 
284     for (PetscInt j = 0; j < dims[1]; j++) array[i * dims[1] + j] = (PetscReal)atof(row_array[j]);
285     PetscCall(PetscStrToArrayDestroy(ndims, row_array));
286   }
287 
288   PetscCall(PetscFClose(comm, fp));
289   PetscFunctionReturn(PETSC_SUCCESS);
290 }
291