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