Line data Source code
1 : //* This file is part of the MOOSE framework
2 : //* https://mooseframework.inl.gov
3 : //*
4 : //* All rights reserved, see COPYRIGHT for full restrictions
5 : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 : //*
7 : //* Licensed under LGPL 2.1, please see LICENSE for details
8 : //* https://www.gnu.org/licenses/lgpl-2.1.html
9 :
10 : // STL includes
11 : #include <sstream>
12 : #include <iomanip>
13 : #include <iterator>
14 : #include <type_traits>
15 :
16 : // MOOSE includes
17 : #include "DelimitedFileReader.h"
18 : #include "MooseUtils.h"
19 : #include "MooseError.h"
20 : #include "pcrecpp.h"
21 :
22 : namespace MooseUtils
23 : {
24 :
25 : template <typename T>
26 1191 : DelimitedFileReaderTempl<T>::DelimitedFileReaderTempl(const std::string & filename,
27 : const libMesh::Parallel::Communicator * comm)
28 1191 : : _filename(filename),
29 1191 : _header_flag(HeaderFlag::AUTO),
30 1191 : _ignore_empty_lines(true),
31 1191 : _communicator(comm),
32 1191 : _format_flag(FormatFlag::COLUMNS)
33 : {
34 1191 : }
35 :
36 : template <typename T>
37 : void
38 1213 : DelimitedFileReaderTempl<T>::read()
39 : {
40 : // Number of columns
41 : std::size_t n_cols;
42 :
43 : // Storage for the raw data
44 1213 : std::vector<T> raw;
45 1213 : std::size_t size_raw = 0;
46 1213 : std::size_t size_offsets = 0;
47 :
48 : // Read data
49 1213 : if (_communicator == nullptr || _communicator->rank() == 0)
50 : {
51 : // Check the file
52 1048 : MooseUtils::checkFileReadable(_filename);
53 :
54 : // Create the file stream and do nothing if the file is empty
55 1043 : std::ifstream stream_data(_filename);
56 1043 : if (stream_data.peek() == std::ifstream::traits_type::eof())
57 5 : return;
58 :
59 : // Read/generate the header
60 1038 : if (_format_flag == FormatFlag::ROWS)
61 677 : readRowData(stream_data, raw);
62 : else
63 361 : readColumnData(stream_data, raw);
64 :
65 : // Set the number of columns
66 1030 : n_cols = _names.size();
67 :
68 : // Close the stream
69 1030 : stream_data.close();
70 :
71 : // Set raw data vector size
72 1030 : size_raw = raw.size();
73 1030 : size_offsets = _row_offsets.size();
74 1043 : }
75 :
76 1195 : if (_communicator != nullptr)
77 : {
78 : // Broadcast column names
79 742 : _communicator->broadcast(n_cols);
80 742 : _names.resize(n_cols);
81 742 : _communicator->broadcast(_names);
82 :
83 : // Broadcast raw data
84 742 : _communicator->broadcast(size_raw);
85 742 : raw.resize(size_raw);
86 742 : _communicator->broadcast(raw);
87 :
88 : // Broadcast row offsets
89 742 : if (_format_flag == FormatFlag::ROWS)
90 : {
91 409 : _communicator->broadcast(size_offsets);
92 409 : _row_offsets.resize(size_offsets);
93 409 : _communicator->broadcast(_row_offsets);
94 : }
95 : }
96 :
97 : // Resize the internal storage
98 1195 : _data.resize(n_cols);
99 :
100 : // Process "row" formatted data
101 1195 : if (_format_flag == FormatFlag::ROWS)
102 : {
103 763 : typename std::vector<T>::iterator start = raw.begin();
104 6637 : for (std::size_t j = 0; j < n_cols; ++j)
105 : {
106 5874 : _data[j] = std::vector<T>(start, start + _row_offsets[j]);
107 5874 : std::advance(start, _row_offsets[j]);
108 : }
109 : }
110 :
111 : // Process "column" formatted data
112 : else
113 : {
114 : mooseAssert(raw.size() % n_cols == 0,
115 : "The raw data is not evenly divisible by the number of columns.");
116 432 : const std::size_t n_rows = raw.size() / n_cols;
117 1874 : for (std::size_t j = 0; j < n_cols; ++j)
118 : {
119 1442 : _data[j].resize(n_rows);
120 267548 : for (std::size_t i = 0; i < n_rows; ++i)
121 266106 : _data[j][i] = raw[i * n_cols + j];
122 : }
123 : }
124 1210 : }
125 :
126 : template <typename T>
127 : std::size_t
128 0 : DelimitedFileReaderTempl<T>::numEntries() const
129 : {
130 0 : std::size_t n_entries = 0;
131 0 : for (std::size_t i = 0; i < _data.size(); ++i)
132 0 : n_entries += _data[i].size();
133 :
134 0 : return n_entries;
135 : }
136 :
137 : template <typename T>
138 : const std::vector<std::string> &
139 477 : DelimitedFileReaderTempl<T>::getNames() const
140 : {
141 477 : return _names;
142 : }
143 :
144 : template <typename T>
145 : const std::vector<std::vector<T>> &
146 1216 : DelimitedFileReaderTempl<T>::getData() const
147 : {
148 1216 : return _data;
149 : }
150 :
151 : template <>
152 : const std::vector<Point>
153 267 : DelimitedFileReaderTempl<double>::getDataAsPoints() const
154 : {
155 267 : std::vector<Point> point_data;
156 :
157 1466 : for (std::size_t i = 0; i < _data.size(); ++i)
158 : {
159 1207 : Point point;
160 :
161 : // Other checks in this class ensure that each data entry has the same number of values;
162 : // here we just need to check that each data set has LIBMESH_DIM entries (which we could do by
163 : // equivalently checking that the total number of entries is divisibly by LIBMESH_DIM
164 : // _and_ one of these data sets has LIBMESH_DIM entries (consider the fringe case where
165 : // LIBMESH_DIM is 3, but you accidentally put a point file like
166 : // 0 0
167 : // 1 0
168 : // 2 0
169 : // where each point is the same length _and_ the total points is still divisible by 3.
170 : // This check here is more exact.
171 1207 : if (_data.at(i).size() != LIBMESH_DIM)
172 8 : mooseError("Each point in file ", _filename, " must have ", LIBMESH_DIM, " entries");
173 :
174 4796 : for (std::size_t j = 0; j < LIBMESH_DIM; ++j)
175 3597 : point(j) = _data.at(i).at(j);
176 :
177 1199 : point_data.push_back(point);
178 : }
179 :
180 259 : return point_data;
181 2 : }
182 :
183 : template <typename T>
184 : const std::vector<Point>
185 0 : DelimitedFileReaderTempl<T>::getDataAsPoints() const
186 : {
187 0 : mooseError("Not implemented");
188 : }
189 :
190 : template <typename T>
191 : const std::vector<T> &
192 110 : DelimitedFileReaderTempl<T>::getData(const std::string & name) const
193 : {
194 110 : const auto it = find(_names.begin(), _names.end(), name);
195 110 : if (it == _names.end())
196 2 : mooseError("Could not find '", name, "' in header of file ", _filename, ".");
197 324 : return _data[std::distance(_names.begin(), it)];
198 : }
199 :
200 : template <typename T>
201 : const std::vector<T> &
202 8755 : DelimitedFileReaderTempl<T>::getData(std::size_t index) const
203 : {
204 8755 : if (index >= _data.size())
205 2 : mooseError("The supplied index ",
206 : index,
207 : " is out-of-range for the available data in file '",
208 2 : _filename,
209 : "' which contains ",
210 2 : _data.size(),
211 : " items.");
212 8753 : return _data[index];
213 : }
214 :
215 : template <typename T>
216 : void
217 361 : DelimitedFileReaderTempl<T>::readColumnData(std::ifstream & stream_data, std::vector<T> & output)
218 : {
219 : // Local storage for the data being read
220 361 : std::string line;
221 361 : std::vector<T> row;
222 :
223 : // Keep track of the line number for error reporting
224 361 : unsigned int count = 0;
225 :
226 : // Number of columns expected based on the first row of the data
227 361 : std::size_t n_cols = INVALID_SIZE;
228 :
229 : // Read the lines
230 102034 : while (std::getline(stream_data, line))
231 : {
232 : // Increment line counter and clear any tokenized data
233 101681 : count++;
234 101681 : row.clear();
235 :
236 : // Ignore empty and/or comment lines, if applicable
237 101681 : if (preprocessLine(line, count))
238 98 : continue;
239 :
240 : // Read header, if the header exists and the column names do not exist.
241 101581 : if (_names.empty() && header(line))
242 : {
243 243 : MooseUtils::tokenize(line, _names, 1, delimiter(line));
244 1151 : for (std::string & str : _names)
245 1816 : str = MooseUtils::trim(str);
246 243 : continue;
247 243 : }
248 :
249 : // Separate the row and error if it fails
250 101338 : processLine(line, row, count);
251 :
252 : // Set the number of columns
253 101334 : if (n_cols == INVALID_SIZE)
254 359 : n_cols = row.size();
255 :
256 : // Check number of columns
257 101334 : if (row.size() != n_cols)
258 2 : mooseError("The number of columns read (",
259 2 : row.size(),
260 : ") does not match the number of columns expected (",
261 : n_cols,
262 : ") based on the first row of the file when reading row ",
263 : count,
264 : " in file ",
265 2 : _filename,
266 : ".");
267 :
268 : // Append data
269 101332 : output.insert(output.end(), row.begin(), row.end());
270 : }
271 :
272 : // If the names have not been assigned, create the default names
273 353 : if (_names.empty())
274 : {
275 116 : _names.resize(n_cols);
276 116 : int padding = MooseUtils::numDigits(n_cols);
277 386 : for (std::size_t i = 0; i < n_cols; ++i)
278 : {
279 270 : std::stringstream ss;
280 270 : ss << "column_" << std::setw(padding) << std::setfill('0') << i;
281 270 : _names[i] = ss.str();
282 : }
283 : }
284 369 : }
285 :
286 : template <typename T>
287 : void
288 677 : DelimitedFileReaderTempl<T>::readRowData(std::ifstream & stream_data, std::vector<T> & output)
289 : {
290 : // Local storage for the data being read
291 677 : std::string line;
292 677 : std::vector<T> row;
293 677 : unsigned int linenum = 0; // line number in file
294 :
295 : // Clear existing data
296 677 : _names.clear();
297 677 : _row_offsets.clear();
298 :
299 : // Read the lines
300 6281 : while (std::getline(stream_data, line))
301 : {
302 : // Increment line counter and clear any tokenized data
303 5604 : linenum++;
304 5604 : row.clear();
305 :
306 : // Ignore empty lines
307 5604 : if (preprocessLine(line, linenum))
308 70 : continue;
309 :
310 5534 : if (header(line))
311 : {
312 70 : std::size_t index = line.find_first_of(delimiter(line));
313 70 : _names.push_back(line.substr(0, index));
314 70 : line = line.substr(index);
315 : }
316 :
317 : // Separate the row and error if it fails
318 5534 : processLine(line, row, linenum);
319 :
320 : // Store row offsets to allow for un-even rows
321 5534 : _row_offsets.push_back(row.size());
322 :
323 : // Append data
324 5534 : output.insert(output.end(), row.begin(), row.end());
325 : }
326 :
327 : // Assign row names if not provided via header
328 677 : if (_names.empty())
329 : {
330 653 : int padding = MooseUtils::numDigits(_row_offsets.size());
331 6117 : for (std::size_t i = 0; i < _row_offsets.size(); ++i)
332 : {
333 5464 : std::stringstream ss;
334 5464 : ss << "row_" << std::setw(padding) << std::setfill('0') << i;
335 5464 : _names.push_back(ss.str());
336 : }
337 : }
338 677 : }
339 :
340 : template <typename T>
341 : bool
342 107285 : DelimitedFileReaderTempl<T>::preprocessLine(std::string & line, const unsigned int & num)
343 : {
344 : // Handle row comments
345 107285 : std::size_t index = _row_comment.empty() ? line.size() : line.find_first_of(_row_comment);
346 107285 : line = MooseUtils::trim(line.substr(0, index));
347 :
348 : // Ignore empty lines
349 107285 : if (line.empty())
350 : {
351 170 : if (_ignore_empty_lines)
352 168 : return true;
353 : else
354 2 : mooseError("Failed to read line ", num, " in file ", _filename, ". The line is empty.");
355 : }
356 107115 : return false;
357 : }
358 :
359 : template <typename T>
360 : void
361 106872 : DelimitedFileReaderTempl<T>::processLine(const std::string & line,
362 : std::vector<T> & row,
363 : const unsigned int & num)
364 : {
365 106872 : std::string line_copy = line;
366 : // Convert booleans to numeric
367 : if constexpr (!std::is_same_v<T, std::string>)
368 : {
369 106822 : line_copy = MooseUtils::toLower(line_copy);
370 427288 : line_copy = MooseUtils::replaceAll(line_copy, "true", "1");
371 320466 : line_copy = MooseUtils::replaceAll(line_copy, "false", "0");
372 : }
373 :
374 : // Separate the row and error if it fails
375 106872 : bool status = MooseUtils::tokenizeAndConvert<T>(line_copy, row, delimiter(line));
376 106872 : if (!status)
377 4 : mooseError("Failed to convert a delimited data into double when reading line ",
378 : num,
379 : " in file ",
380 4 : _filename,
381 : ".\n LINE ",
382 : num,
383 : ": ",
384 : line);
385 106872 : }
386 :
387 : template <typename T>
388 : const std::string &
389 108144 : DelimitedFileReaderTempl<T>::delimiter(const std::string & line)
390 : {
391 108144 : if (_delimiter.empty())
392 : {
393 965 : if (line.find(",") != std::string::npos)
394 611 : _delimiter = ",";
395 354 : else if (line.find("\t") != std::string::npos)
396 2 : _delimiter = "\t";
397 : else
398 352 : _delimiter = " ";
399 : }
400 108144 : return _delimiter;
401 : }
402 :
403 : template <typename T>
404 : bool
405 106132 : DelimitedFileReaderTempl<T>::header(const std::string & line)
406 : {
407 106132 : switch (_header_flag)
408 : {
409 105086 : case HeaderFlag::OFF:
410 105086 : return false;
411 87 : case HeaderFlag::ON:
412 87 : return true;
413 959 : default:
414 :
415 : // Attempt to convert the line, if it fails assume it is a header
416 959 : std::vector<double> row;
417 959 : bool contains_alpha = !MooseUtils::tokenizeAndConvert<double>(line, row, delimiter(line));
418 :
419 : // Based on auto detect set the flag to TRUE|FALSE to short-circuit this check for each line
420 : // in the case of row data.
421 959 : _header_flag = contains_alpha ? HeaderFlag::ON : HeaderFlag::OFF;
422 959 : return contains_alpha;
423 959 : }
424 : }
425 :
426 : template class DelimitedFileReaderTempl<Real>;
427 : template class DelimitedFileReaderTempl<std::string>;
428 : } // MooseUtils
|