xref: /honee/src/honee-file.c (revision 56fab57df9ac69fed96d484579f434de0b06d0cb)
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     *out = val;
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   PetscInt32    token;
131   PetscReal     file_time;
132   PetscDataType file_type = PETSC_INT32;
133   MPI_Comm      comm      = PetscObjectComm((PetscObject)viewer);
134 
135   PetscFunctionBeginUser;
136   PetscCall(PetscViewerBinaryRead(viewer, &token, 1, NULL, PETSC_INT32));
137   if (token == HONEE_FILE_TOKEN_32 || token == HONEE_FILE_TOKEN_64 ||
138       token == HONEE_FILE_TOKEN) {  // New style format; we're reading a file with step number and time in the header
139     if (token == HONEE_FILE_TOKEN_32) file_type = PETSC_INT32;
140     else if (token == HONEE_FILE_TOKEN_64) file_type = PETSC_INT64;
141     PetscCall(BinaryReadIntoInt(viewer, &file_step_number, file_type));
142     PetscCall(PetscViewerBinaryRead(viewer, &file_time, 1, NULL, PETSC_REAL));
143   } else if (token == VEC_FILE_CLASSID) {  // Legacy format of just the vector, encoded as [VEC_FILE_CLASSID, length, ]
144     // NOTE: Only used for regression testing now
145     PetscInt length, N;
146     PetscCall(BinaryReadIntoInt(viewer, &length, file_type));
147     PetscCall(VecGetSize(Q, &N));
148     PetscCheck(length == N, comm, PETSC_ERR_ARG_INCOMP, "File Vec has length %" PetscInt_FMT " but DM has global Vec size %" PetscInt_FMT, length, N);
149     PetscCall(PetscViewerBinarySetSkipHeader(viewer, PETSC_TRUE));
150     file_time        = 0;
151     file_step_number = 0;
152   } else SETERRQ(comm, PETSC_ERR_FILE_UNEXPECTED, "Not a HONEE header token or a PETSc Vec in file");
153 
154   if (time) *time = file_time;
155   if (step_number) *step_number = file_step_number;
156   PetscCall(VecLoad(Q, viewer));
157   PetscFunctionReturn(PETSC_SUCCESS);
158 }
159 
160 /**
161   @brief Write vector to binary file with solution time and step number
162 
163   @param[in] viewer      `PetscViewer` for binary file. Must be binary viewer and in write mode
164   @param[in] Q           `Vec` of the solution
165   @param[in] time        Solution time of the `Vec`
166   @param[in] step_number Timestep number of the Vec
167 **/
168 PetscErrorCode HoneeWriteBinaryVec(PetscViewer viewer, Vec Q, PetscReal time, PetscInt step_number) {
169   PetscInt32 token = PetscDefined(USE_64BIT_INDICES) ? HONEE_FILE_TOKEN_64 : HONEE_FILE_TOKEN_32;
170 
171   PetscFunctionBeginUser;
172   {  // Verify viewer is in correct state
173     PetscViewerType viewer_type;
174     PetscFileMode   file_mode;
175     PetscBool       is_binary_viewer;
176     MPI_Comm        comm = PetscObjectComm((PetscObject)viewer);
177 
178     PetscCall(PetscViewerGetType(viewer, &viewer_type));
179     PetscCall(PetscStrcmp(viewer_type, PETSCVIEWERBINARY, &is_binary_viewer));
180     PetscCheck(is_binary_viewer, comm, PETSC_ERR_ARG_WRONGSTATE, "Viewer must be binary type; instead got %s", viewer_type);
181     PetscCall(PetscViewerFileGetMode(viewer, &file_mode));
182     PetscCheck(file_mode == FILE_MODE_WRITE, comm, PETSC_ERR_ARG_WRONGSTATE, "Viewer must be binary type; instead got %s",
183                file_mode == -1 ? "UNDEFINED" : PetscFileModes[file_mode]);
184   }
185 
186   PetscCall(PetscViewerBinaryWrite(viewer, &token, 1, PETSC_INT32));
187   PetscCall(PetscViewerBinaryWrite(viewer, &step_number, 1, PETSC_INT));
188   PetscCall(PetscViewerBinaryWrite(viewer, &time, 1, PETSC_REAL));
189   PetscCall(VecView(Q, viewer));
190   PetscFunctionReturn(PETSC_SUCCESS);
191 }
192 
193 /**
194   @brief Open a PHASTA *.dat file, grabbing dimensions and file pointer
195 
196   This function opens the file specified by `path` using `PetscFOpen` and passes the file pointer in `fp`.
197   It is not closed in this function, thus `fp` must be closed sometime after this function has been called (using `PetscFClose` for example).
198 
199   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.
200 
201   @param[in]  comm           MPI_Comm for the program
202   @param[in]  path           Path to the file
203   @param[in]  char_array_len Length of the character array that should contain each line
204   @param[out] dims           Dimensions of the file, taken from the first line of the file
205   @param[out] fp File        pointer to the opened file
206 **/
207 PetscErrorCode PhastaDatFileOpen(const MPI_Comm comm, const char path[], const PetscInt char_array_len, PetscInt dims[2], FILE **fp) {
208   int    ndims;
209   char   line[char_array_len];
210   char **array;
211 
212   PetscFunctionBeginUser;
213   PetscCall(PetscFOpen(comm, path, "r", fp));
214   PetscCall(PetscSynchronizedFGets(comm, *fp, char_array_len, line));
215   PetscCall(PetscStrToArray(line, ' ', &ndims, &array));
216   PetscCheck(ndims == 2, comm, PETSC_ERR_FILE_UNEXPECTED, "Found %d dimensions instead of 2 on the first line of %s", ndims, path);
217 
218   for (PetscInt i = 0; i < ndims; i++) dims[i] = atoi(array[i]);
219   PetscCall(PetscStrToArrayDestroy(ndims, array));
220   PetscFunctionReturn(PETSC_SUCCESS);
221 }
222 
223 /**
224   @brief Get the number of rows for the PHASTA file at path.
225 
226   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.
227 
228   @param[in]  comm  `MPI_Comm` for the program
229   @param[in]  path  Path to the file
230   @param[out] nrows Number of rows
231 **/
232 PetscErrorCode PhastaDatFileGetNRows(const MPI_Comm comm, const char path[], PetscInt *nrows) {
233   const PetscInt char_array_len = 512;
234   PetscInt       dims[2];
235   FILE          *fp;
236 
237   PetscFunctionBeginUser;
238   PetscCall(PhastaDatFileOpen(comm, path, char_array_len, dims, &fp));
239   *nrows = dims[0];
240   PetscCall(PetscFClose(comm, fp));
241   PetscFunctionReturn(PETSC_SUCCESS);
242 }
243 
244 /**
245   @brief Read PetscReal values from PHASTA file
246 
247   @param[in]  comm  `MPI_Comm` for the program
248   @param[in]  path  Path to the file
249   @param[out] array Pointer to allocated array of correct size
250 **/
251 PetscErrorCode PhastaDatFileReadToArrayReal(MPI_Comm comm, const char path[], PetscReal array[]) {
252   PetscInt       dims[2];
253   FILE          *fp;
254   const PetscInt char_array_len = 512;
255   char           line[char_array_len];
256 
257   PetscFunctionBeginUser;
258   PetscCall(PhastaDatFileOpen(comm, path, char_array_len, dims, &fp));
259 
260   for (PetscInt i = 0; i < dims[0]; i++) {
261     int    ndims;
262     char **row_array;
263 
264     PetscCall(PetscSynchronizedFGets(comm, fp, char_array_len, line));
265     PetscCall(PetscStrToArray(line, ' ', &ndims, &row_array));
266     PetscCheck(ndims == dims[1], comm, PETSC_ERR_FILE_UNEXPECTED,
267                "Line %" PetscInt_FMT " of %s does not contain enough columns (%d instead of %" PetscInt_FMT ")", i, path, ndims, dims[1]);
268 
269     for (PetscInt j = 0; j < dims[1]; j++) array[i * dims[1] + j] = (PetscReal)atof(row_array[j]);
270     PetscCall(PetscStrToArrayDestroy(ndims, row_array));
271   }
272 
273   PetscCall(PetscFClose(comm, fp));
274   PetscFunctionReturn(PETSC_SUCCESS);
275 }
276