GCC Code Coverage Report


Directory: gridformat/
File: gridformat/writer.hpp
Date: 2024-11-10 16:24:00
Exec Total Coverage
Lines: 86 90 95.6%
Functions: 152 168 90.5%
Branches: 48 118 40.7%

Line Branch Exec Source
1 // SPDX-FileCopyrightText: 2022-2023 Dennis Gläser <dennis.glaeser@iws.uni-stuttgart.de>
2 // SPDX-License-Identifier: MIT
3 /*!
4 * \file
5 * \ingroup API
6 * \brief A generic writer providing access to the writers for all supported formats.
7 */
8 #ifndef GRIDFORMAT_WRITER_HPP_
9 #define GRIDFORMAT_WRITER_HPP_
10
11 #include <string>
12 #include <memory>
13 #include <utility>
14 #include <functional>
15 #include <type_traits>
16
17 #include <gridformat/common/exceptions.hpp>
18 #include <gridformat/grid/writer.hpp>
19 #include <gridformat/grid/concepts.hpp>
20 #include <gridformat/grid/type_traits.hpp>
21 #include <gridformat/parallel/concepts.hpp>
22
23 namespace GridFormat {
24
25 template<typename FileFormat>
26 struct WriterFactory;
27
28
29 #ifndef DOXYGEN
30 namespace WriterDetail {
31
32 template<typename FileFormat, typename Grid>
33 static constexpr bool has_sequential_factory
34 = is_complete<WriterFactory<FileFormat>>
35 and Concepts::Grid<Grid>
36 and requires(const FileFormat& f, const Grid& grid) {
37 { WriterFactory<FileFormat>::make(f, grid) } -> std::derived_from<GridWriter<Grid>>;
38 };
39
40 template<typename FileFormat, typename Grid>
41 static constexpr bool has_sequential_time_series_factory
42 = is_complete<WriterFactory<FileFormat>>
43 and Concepts::Grid<Grid>
44 and requires(const FileFormat& f, const Grid& grid) {
45 { WriterFactory<FileFormat>::make(f, grid, std::string{}) } -> std::derived_from<TimeSeriesGridWriter<Grid>>;
46 };
47
48 template<typename FileFormat, typename Grid, typename Comm>
49 static constexpr bool has_parallel_factory
50 = is_complete<WriterFactory<FileFormat>>
51 and Concepts::Grid<Grid>
52 and Concepts::Communicator<Comm>
53 and requires(const FileFormat& f, const Grid& grid, const Comm& comm) {
54 { WriterFactory<FileFormat>::make(f, grid, comm) } -> std::derived_from<GridWriter<Grid>>;
55 };
56
57 template<typename FileFormat, typename Grid, typename Comm>
58 static constexpr bool has_parallel_time_series_factory
59 = is_complete<WriterFactory<FileFormat>>
60 and Concepts::Grid<Grid>
61 and Concepts::Communicator<Comm>
62 and requires(const FileFormat& f, const Grid& grid, const Comm& comm) {
63 { WriterFactory<FileFormat>::make(f, grid, comm, std::string{}) } -> std::derived_from<TimeSeriesGridWriter<Grid>>;
64 };
65
66 template<typename _Grid, typename Grid>
67 concept Compatible = std::same_as<std::remove_cvref_t<_Grid>, Grid> and std::is_lvalue_reference_v<_Grid>;
68
69 } // namespace WriterDetail
70 #endif // DOXYGEN
71
72
73 /*!
74 * \ingroup API
75 * \brief Interface to the writers for all supported file formats.
76 * Depending on the chosen format, this exposes the interface of
77 * grid file or time series writers.
78 * \details Typically you would construct this class with one of the predefined
79 * file format instances. For example, with the .vtu file format:
80 * \code{.cpp}
81 * GridFormat::Writer writer{GridFormat::vtu, grid};
82 * \endcode
83 * \note The constructor checks that the grid you are passing in is
84 * actually an lvalue-reference. If not, compilation will fail.
85 * This is because all writers take the grid per reference and
86 * their lifetime is bound to the lifetime of the given grid.
87 * \tparam Grid The type of grid which should be written out.
88 */
89 template<Concepts::Grid G>
90 class Writer {
91 public:
92 using Grid = G;
93
94 /*!
95 * \brief Construct a sequential grid file writer.
96 * \param f The file format which should be written.
97 * \param grid The grid which should be written out.
98 */
99 template<typename FileFormat, WriterDetail::Compatible<Grid> _Grid>
100 requires(WriterDetail::has_sequential_factory<FileFormat, Grid>)
101 42 Writer(const FileFormat& f, _Grid&& grid)
102
2/4
✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 21 times.
✗ Branch 5 not taken.
42 : Writer(WriterFactory<FileFormat>::make(f, grid))
103 42 {}
104
105 /*!
106 * \brief Construct a sequential time series writer.
107 * \param f The file format which should be written.
108 * \param grid The grid which should be written out.
109 * \param base_filename The name of the file (without extension) into which to write.
110 */
111 template<typename FileFormat, WriterDetail::Compatible<Grid> _Grid>
112 requires(WriterDetail::has_sequential_time_series_factory<FileFormat, Grid>)
113 25 Writer(const FileFormat& f, _Grid&& grid, const std::string& base_filename)
114
2/4
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
25 : Writer(WriterFactory<FileFormat>::make(f, grid, base_filename))
115 25 {}
116
117 /*!
118 * \brief Construct a parallel grid file writer.
119 * \param f The file format which should be written.
120 * \param grid The grid which should be written out.
121 * \param comm The communicator for parallel communication.
122 */
123 template<typename FileFormat, WriterDetail::Compatible<Grid> _Grid, Concepts::Communicator Comm>
124 requires(WriterDetail::has_parallel_factory<FileFormat, Grid, Comm>)
125 56 Writer(const FileFormat& f, _Grid&& grid, const Comm& comm)
126
2/4
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
56 : Writer(WriterFactory<FileFormat>::make(f, grid, comm))
127 56 {}
128
129 /*!
130 * \brief Construct a parallel time series file writer.
131 * \param f The file format which should be written.
132 * \param grid The grid which should be written out.
133 * \param comm The communicator for parallel communication.
134 * \param base_filename The name of the file (without extension) into which to write.
135 */
136 template<typename FileFormat, WriterDetail::Compatible<Grid> _Grid, Concepts::Communicator Comm>
137 requires(WriterDetail::has_parallel_time_series_factory<FileFormat, Grid, Comm>)
138 54 Writer(const FileFormat& f, _Grid&& grid, const Comm& comm, const std::string& base_filename)
139
2/4
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
54 : Writer(WriterFactory<FileFormat>::make(f, grid, comm, base_filename))
140 54 {}
141
142 //! Construct a grid file writer from a writer implementation
143 template<std::derived_from<GridWriter<Grid>> W>
144 requires(!std::is_lvalue_reference_v<W>)
145 98 explicit Writer(W&& writer)
146
1/2
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
98 : _writer(std::make_unique<W>(std::move(writer)))
147 98 {}
148
149 //! Construct a time series file writer from a writer implementation
150 template<std::derived_from<TimeSeriesGridWriter<Grid>> W>
151 requires(!std::is_lvalue_reference_v<W>)
152 79 explicit Writer(W&& writer)
153
1/2
✓ Branch 3 taken 41 times.
✗ Branch 4 not taken.
79 : _time_series_writer(std::make_unique<W>(std::move(writer)))
154 79 {}
155
156 /*!
157 * \brief Write the grid and data to a file.
158 * \param filename The name of file into which to write (without extension).
159 * \note Calling this function is only allowed if the writer was created as
160 * a grid file writer. If this instance is a time series writer, calling
161 * this function will throw an exception.
162 */
163 43 std::string write(const std::string& filename) const {
164
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 43 times.
43 if (!_writer)
165 throw InvalidState(
166 "Writer was constructed as a time series writer. Only write(Scalar) can be used."
167 );
168 43 return _writer->write(filename);
169 }
170
171 /*!
172 * \brief Write a time step in a time series.
173 * \param time_value The time corresponding to this time step.
174 * \note Calling this function is only allowed if the writer was created as
175 * a time series file writer. If this instance is a grid file writer,
176 * calling this function will throw an exception.
177 */
178 template<Concepts::Scalar T>
179 129 std::string write(const T& time_value) const {
180
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 129 times.
129 if (!_time_series_writer)
181 throw InvalidState(
182 "Writer was not constructed as a time series writer. Only write(std::string) can be used."
183 );
184 129 return _time_series_writer->write(time_value);
185 }
186
187 /*!
188 * \brief Set a meta data field to be added to the output.
189 * \param name The name of the meta data field.
190 * \param field The actual meta data.
191 * \note Supported metadata are scalar values, strings, or ranges of scalars.
192 */
193 template<typename Field>
194 430 void set_meta_data(const std::string& name, Field&& field) {
195
1/2
✓ Branch 1 taken 215 times.
✗ Branch 2 not taken.
860 _visit_writer([&] (auto& writer) {
196
4/12
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 7 taken 38 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 12 times.
✗ Branch 11 not taken.
✓ Branch 14 taken 18 times.
✗ Branch 15 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✓ Branch 28 taken 11 times.
✗ Branch 29 not taken.
215 writer.set_meta_data(name, std::forward<Field>(field));
197 });
198 430 }
199
200 /*!
201 * \brief Set a point data field to be added to the output.
202 * \param name The name of the point data field.
203 * \param field The actual point data.
204 * \note Point data is usually given as lambdas that are invocable
205 * with points of the grid. You can also pass in custom fields
206 * that inherit from the `Field` class. This is discouraged,
207 * however.
208 */
209 template<typename Field>
210 222 void set_point_field(const std::string& name, Field&& field) {
211
1/2
✓ Branch 1 taken 177 times.
✗ Branch 2 not taken.
840 _visit_writer([&] (auto& writer) {
212
9/29
✓ Branch 2 taken 114 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 6 taken 21 times.
✗ Branch 7 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
✓ Branch 14 taken 3 times.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✓ Branch 20 taken 11 times.
✗ Branch 21 not taken.
✓ Branch 22 taken 3 times.
✗ Branch 23 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✓ Branch 30 taken 3 times.
✗ Branch 31 not taken.
✗ Branch 34 not taken.
✗ Branch 35 not taken.
✓ Branch 38 taken 3 times.
✗ Branch 39 not taken.
✓ Branch 42 taken 15 times.
✗ Branch 43 not taken.
✗ Branch 46 not taken.
✗ Branch 47 not taken.
177 writer.set_point_field(name, std::forward<Field>(field));
213 });
214 222 }
215
216 /*!
217 * \brief Overload with custom precision with which to write the field.
218 * \note Can be used to save space on disk and increase the write speed
219 * if you know that your field can be represented sufficiently well
220 * by a smaller precision.
221 */
222 template<typename Field, typename T>
223 void set_point_field(const std::string& name, Field&& field, const Precision<T>& prec) {
224 _visit_writer([&] (auto& writer) {
225 writer.set_point_field(name, std::forward<Field>(field), prec);
226 });
227 }
228
229 /*!
230 * \brief Set a cell data field to be added to the output.
231 * \param name The name of the cell data field.
232 * \param field The actual cell data.
233 * \note Cell data is usually given as lambdas that are invocable
234 * with cells of the grid. You can also pass in custom fields
235 * that inherit from the `Field` class. This is discouraged,
236 * however.
237 */
238 template<typename Field>
239 177 void set_cell_field(const std::string& name, Field&& field) {
240
1/2
✓ Branch 1 taken 162 times.
✗ Branch 2 not taken.
795 _visit_writer([&] (auto& writer) {
241
4/11
✓ Branch 2 taken 129 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 6 taken 18 times.
✗ Branch 7 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✓ Branch 20 taken 11 times.
✗ Branch 21 not taken.
162 writer.set_cell_field(name, std::forward<Field>(field));
242 });
243 177 }
244
245 /*!
246 * \brief Overload with custom precision with which to write the field.
247 * \note Can be used to save space on disk and increase the write speed
248 * if you know that your field can be represented sufficiently well
249 * by a smaller precision.
250 */
251 template<typename Field, typename T>
252 void set_cell_field(const std::string& name, Field&& field, const Precision<T>& prec) {
253 _visit_writer([&] (auto& writer) {
254 writer.set_cell_field(name, std::forward<Field>(field), prec);
255 });
256 }
257
258 /*!
259 * \brief Remove a meta data field from the output.
260 * \param name The name of the meta data field.
261 */
262 1 FieldPtr remove_meta_data(const std::string& name) {
263 3 return _visit_writer([&] (auto& writer) {
264 1 return writer.remove_meta_data(name);
265
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 });
266 }
267
268 /*!
269 * \brief Remove a point field from the output.
270 * \param name The name of the point field.
271 */
272 1 FieldPtr remove_point_field(const std::string& name) {
273 3 return _visit_writer([&] (auto& writer) {
274 1 return writer.remove_point_field(name);
275
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 });
276 }
277
278 /*!
279 * \brief Remove a cell field from the output.
280 * \param name The name of the cell field.
281 */
282 1 FieldPtr remove_cell_field(const std::string& name) {
283 3 return _visit_writer([&] (auto& writer) {
284 1 return writer.remove_cell_field(name);
285
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 });
286 }
287
288 //! Remove all data inserted to the writer.
289 1 void clear() {
290
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
5 _visit_writer([&] (auto& writer) {
291 1 writer.clear();
292 });
293 1 }
294
295 //! Ignore/consider warnings (default: true)
296 void set_ignore_warnings(bool value) {
297 _visit_writer([&] (auto& writer) {
298 writer.set_ignore_warnings(value);
299 });
300 }
301
302 /*!
303 * \brief Copy all inserted fields into another writer.
304 * \param out The writer into which to copy all fields of this writer.
305 */
306 template<typename Writer>
307 4 void copy_fields(Writer& out) const {
308
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
20 _visit_writer([&] (const auto& writer) {
309 4 writer.copy_fields(out);
310 });
311 4 }
312
313 /*!
314 * \brief Return the basic options used by this writer.
315 * \note This is used internally and not be required by users.
316 */
317 8 const std::optional<WriterOptions>& writer_options() const {
318
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
32 return _visit_writer([&] (const auto& writer) -> const std::optional<WriterOptions>& {
319 8 return writer.writer_options();
320 16 });
321 }
322
323 /*!
324 * \brief Return a reference to the underlying grid.
325 */
326 67 const Grid& grid() const {
327
1/2
✓ Branch 1 taken 67 times.
✗ Branch 2 not taken.
268 return _visit_writer([&] (const auto& writer) -> const Grid& {
328 67 return writer.grid();
329 134 });
330 }
331
332 /*!
333 * \brief Return a range over all point fields that were added to the given writer.
334 * \param w The writer whose fields to return
335 * \return A range over key-value pairs containing the name and a pointer to actual field.
336 * \note You can use range-based for loops with structured bindings, for instance:
337 * \code{.cpp}
338 * for (const auto& [name, field_ptr] : point_fields(writer)) { ... }
339 * \endcode
340 */
341 4 friend decltype(auto) point_fields(const Writer& w) {
342
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
16 return w._visit_writer([&] (const auto& writer) {
343 4 return point_fields(writer);
344 8 });
345 }
346
347 /*!
348 * \brief Return a range over all cell fields that were added to the given writer.
349 * \param w The writer whose fields to return
350 * \return A range over key-value pairs containing the name and a pointer to actual field.
351 * \note You can use range-based for loops with structured bindings, for instance:
352 * \code{.cpp}
353 * for (const auto& [name, field_ptr] : cell_fields(writer)) { ... }
354 * \endcode
355 */
356 4 friend decltype(auto) cell_fields(const Writer& w) {
357
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
16 return w._visit_writer([&] (const auto& writer) {
358 4 return cell_fields(writer);
359 8 });
360 }
361
362 /*!
363 * \brief Return a range over all meta data fields that were added to the given writer.
364 * \param w The writer whose fields to return
365 * \return A range over key-value pairs containing the name and a pointer to actual field.
366 * \note You can use range-based for loops with structured bindings, for instance:
367 * \code{.cpp}
368 * for (const auto& [name, field_ptr] : meta_data_fields(writer)) { ... }
369 * \endcode
370 */
371 4 friend decltype(auto) meta_data_fields(const Writer& w) {
372
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
16 return w._visit_writer([&] (const auto& writer) {
373 4 return meta_data_fields(writer);
374 8 });
375 }
376
377 private:
378 template<typename Visitor>
379 1071 decltype(auto) _visit_writer(const Visitor& visitor) {
380
2/2
✓ Branch 1 taken 171 times.
✓ Branch 2 taken 342 times.
1071 if (_writer)
381 357 return std::invoke(visitor, *_writer);
382 else {
383
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 342 times.
714 if (!_time_series_writer)
384 throw InvalidState("No writer set");
385 714 return std::invoke(visitor, *_time_series_writer);
386 }
387 }
388
389 template<typename Visitor>
390 91 decltype(auto) _visit_writer(const Visitor& visitor) const {
391
2/2
✓ Branch 1 taken 18 times.
✓ Branch 2 taken 38 times.
91 if (_writer)
392 53 return std::invoke(visitor, *_writer);
393 else {
394
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
38 if (!_time_series_writer)
395 throw InvalidState("No writer set");
396 38 return std::invoke(visitor, *_time_series_writer);
397 }
398 }
399
400 std::unique_ptr<GridWriter<Grid>> _writer{nullptr};
401 std::unique_ptr<TimeSeriesGridWriter<Grid>> _time_series_writer{nullptr};
402 };
403
404 template<typename F, typename G>
405 requires(Concepts::Grid<std::remove_cvref_t<G>>)
406 Writer(const F&, G&&) -> Writer<std::remove_cvref_t<G>>;
407
408 template<typename F, typename G, Concepts::Communicator C>
409 requires(Concepts::Grid<std::remove_cvref_t<G>>)
410 Writer(const F&, G&&, const C&) -> Writer<std::remove_cvref_t<G>>;
411
412 template<typename F, typename G>
413 requires(Concepts::Grid<std::remove_cvref_t<G>>)
414 Writer(const F&, G&&, const std::string&) -> Writer<std::remove_cvref_t<G>>;
415
416 template<typename F, typename G, Concepts::Communicator C>
417 requires(Concepts::Grid<std::remove_cvref_t<G>>)
418 Writer(const F&, G&&, const C&, const std::string&) -> Writer<std::remove_cvref_t<G>>;
419
420 } // namespace GridFormat
421
422 #endif // GRIDFORMAT_WRITER_HPP_
423