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 Grid | ||
6 | * \brief Converter between grid formats. | ||
7 | */ | ||
8 | #ifndef GRIDFORMAT_GRID_CONVERTER_HPP_ | ||
9 | #define GRIDFORMAT_GRID_CONVERTER_HPP_ | ||
10 | |||
11 | #include <array> | ||
12 | #include <vector> | ||
13 | #include <ranges> | ||
14 | #include <concepts> | ||
15 | #include <utility> | ||
16 | #include <numeric> | ||
17 | #include <optional> | ||
18 | #include <cstdint> | ||
19 | |||
20 | #include <gridformat/common/field.hpp> | ||
21 | #include <gridformat/common/exceptions.hpp> | ||
22 | |||
23 | #include <gridformat/grid/cell_type.hpp> | ||
24 | #include <gridformat/grid/reader.hpp> | ||
25 | #include <gridformat/grid/writer.hpp> | ||
26 | |||
27 | namespace GridFormat { | ||
28 | |||
29 | #ifndef DOXYGEN | ||
30 | namespace ConverterDetail { | ||
31 | |||
32 | struct ConverterGrid { | ||
33 | const GridReader& reader; | ||
34 | std::vector<std::array<double, 3>> points; | ||
35 | std::vector<std::pair<CellType, std::vector<std::size_t>>> cells; | ||
36 | |||
37 | 35 | explicit ConverterGrid(const GridReader& r) : reader{r} {} | |
38 | |||
39 | 81 | void make_grid() { | |
40 | 81 | points.clear(); | |
41 | 81 | cells.clear(); | |
42 | 81 | _make_points(); | |
43 | 81 | _make_cells(); | |
44 | 81 | } | |
45 | |||
46 | private: | ||
47 | 81 | void _make_points() { | |
48 |
1/2✓ Branch 1 taken 81 times.
✗ Branch 2 not taken.
|
81 | const auto in_points = reader.points(); |
49 |
1/2✓ Branch 2 taken 81 times.
✗ Branch 3 not taken.
|
81 | const auto in_layout = in_points->layout(); |
50 |
1/2✓ Branch 1 taken 81 times.
✗ Branch 2 not taken.
|
81 | const auto in_np = in_layout.extent(0); |
51 |
3/6✓ Branch 1 taken 81 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 81 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 81 times.
✗ Branch 7 not taken.
|
81 | const auto in_dim = in_layout.dimension() > 1 ? in_layout.extent(1) : 0; |
52 |
2/4✓ Branch 1 taken 81 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 81 times.
|
81 | if (in_np != reader.number_of_points()) |
53 | ✗ | throw SizeError("Mismatch between stored and defined number of points."); | |
54 | |||
55 |
1/2✓ Branch 1 taken 81 times.
✗ Branch 2 not taken.
|
81 | points.reserve(in_np); |
56 |
1/2✓ Branch 2 taken 81 times.
✗ Branch 3 not taken.
|
243 | in_points->visit_field_values([&] <typename T> (std::span<const T> values) { |
57 |
2/4✓ Branch 1 taken 81 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 81 times.
✗ Branch 5 not taken.
|
25646 | std::ranges::for_each(std::views::iota(std::size_t{0}, in_np), [&] (auto p_idx) { |
58 |
1/22✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
✗ Branch 33 not taken.
✗ Branch 34 not taken.
✓ Branch 37 taken 12742 times.
✗ Branch 38 not taken.
✗ Branch 41 not taken.
✗ Branch 42 not taken.
|
12742 | points.push_back({}); |
59 |
2/44✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 31 not taken.
✗ Branch 32 not taken.
✗ Branch 34 not taken.
✗ Branch 35 not taken.
✗ Branch 37 not taken.
✗ Branch 38 not taken.
✗ Branch 40 not taken.
✗ Branch 41 not taken.
✗ Branch 43 not taken.
✗ Branch 44 not taken.
✗ Branch 46 not taken.
✗ Branch 47 not taken.
✗ Branch 49 not taken.
✗ Branch 50 not taken.
✗ Branch 52 not taken.
✗ Branch 53 not taken.
✓ Branch 55 taken 12742 times.
✗ Branch 56 not taken.
✓ Branch 58 taken 12742 times.
✗ Branch 59 not taken.
✗ Branch 61 not taken.
✗ Branch 62 not taken.
✗ Branch 64 not taken.
✗ Branch 65 not taken.
|
89194 | std::ranges::for_each(std::views::iota(std::size_t{0}, in_dim), [&] (auto dim) { |
60 | 38226 | points.back().at(dim) = static_cast<double>(values[p_idx*in_dim + dim]); | |
61 | }); | ||
62 | }); | ||
63 | 162 | }); | |
64 | 81 | } | |
65 | |||
66 | 81 | void _make_cells() { | |
67 | 81 | cells.reserve(reader.number_of_cells()); | |
68 |
1/2✓ Branch 2 taken 81 times.
✗ Branch 3 not taken.
|
81 | reader.visit_cells([&] (CellType ct, std::vector<std::size_t> corners) { |
69 |
1/2✓ Branch 4 taken 10380 times.
✗ Branch 5 not taken.
|
10380 | cells.emplace_back(std::pair{std::move(ct), std::move(corners)}); |
70 | 10380 | }); | |
71 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 81 times.
|
81 | if (cells.size() != reader.number_of_cells()) |
72 | ✗ | throw SizeError("Mismatch between stored and defined number of cells."); | |
73 | 81 | } | |
74 | }; | ||
75 | |||
76 | template<typename T> | ||
77 | concept Writer | ||
78 | = requires { typename std::remove_cvref_t<T>::Grid; } | ||
79 | and std::derived_from<std::remove_cvref_t<T>, GridWriterBase<typename T::Grid>>; | ||
80 | |||
81 | template<typename T> | ||
82 | concept PieceWriter = Writer<T> and std::derived_from<std::remove_cvref_t<T>, GridWriter<typename T::Grid>>; | ||
83 | |||
84 | template<typename T> | ||
85 | concept TimeSeriesWriter = Writer<T> and std::derived_from<std::remove_cvref_t<T>, TimeSeriesGridWriter<typename T::Grid>>; | ||
86 | |||
87 | template<typename T> | ||
88 | concept PieceWriterFactory = requires (const T& factory, const ConverterGrid& grid) { | ||
89 | { factory(grid) } -> PieceWriter; | ||
90 | }; | ||
91 | |||
92 | template<typename T> | ||
93 | concept TimeSeriesWriterFactory = requires (const T& factory, const ConverterGrid& grid, const std::string& filename) { | ||
94 | { factory(grid) } -> TimeSeriesWriter; | ||
95 | }; | ||
96 | |||
97 | template<typename T> | ||
98 | concept WriterFactory = PieceWriterFactory<T> or TimeSeriesWriterFactory<T>; | ||
99 | |||
100 | template<typename Reader, Writer Writer> | ||
101 | 166 | void add_piece_fields(const Reader& reader, Writer& writer) { | |
102 | 166 | writer.clear(); | |
103 |
6/10✓ Branch 1 taken 83 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 83 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 83 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 117 times.
✗ Branch 11 not taken.
✓ Branch 16 taken 117 times.
✓ Branch 17 taken 83 times.
|
400 | for (auto [name, field_ptr] : cell_fields(reader)) |
104 |
1/2✓ Branch 4 taken 117 times.
✗ Branch 5 not taken.
|
234 | writer.set_cell_field(std::move(name), std::move(field_ptr)); |
105 |
6/10✓ Branch 1 taken 83 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 83 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 83 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 186 times.
✗ Branch 11 not taken.
✓ Branch 16 taken 186 times.
✓ Branch 17 taken 83 times.
|
538 | for (auto [name, field_ptr] : point_fields(reader)) |
106 |
1/2✓ Branch 4 taken 186 times.
✗ Branch 5 not taken.
|
372 | writer.set_point_field(std::move(name), std::move(field_ptr)); |
107 |
4/10✓ Branch 1 taken 83 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 83 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 83 times.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 16 not taken.
✓ Branch 17 taken 83 times.
|
166 | for (auto [name, field_ptr] : meta_data_fields(reader)) |
108 | ✗ | writer.set_meta_data(std::move(name), std::move(field_ptr)); | |
109 | 166 | } | |
110 | |||
111 | template<typename Reader, PieceWriter Writer> | ||
112 | 44 | std::string write_piece(const Reader& reader, Writer& writer, const std::string& filename) { | |
113 | 44 | add_piece_fields(reader, writer); | |
114 | 44 | return writer.write(filename); | |
115 | } | ||
116 | |||
117 | template<typename Reader, TimeSeriesWriter Writer> | ||
118 | 105 | std::string write_piece(const Reader& reader, Writer& writer, double time_step) { | |
119 | 105 | add_piece_fields(reader, writer); | |
120 | 105 | return writer.write(time_step); | |
121 | } | ||
122 | |||
123 | } // namespace ConverterDetail | ||
124 | #endif // DOXYGEN | ||
125 | |||
126 | /*! | ||
127 | * \ingroup Grid | ||
128 | * \brief Convert between grid formats. | ||
129 | * \param reader A grid reader on which a file was opened. | ||
130 | * \param factory A factory to construct a writer with the desired output format. | ||
131 | * \param filename The name of the file to be written. | ||
132 | */ | ||
133 | template<std::derived_from<GridReader> Reader, ConverterDetail::WriterFactory Factory> | ||
134 | 44 | std::string convert(const Reader& reader, const std::string& filename, const Factory& factory) { | |
135 | 44 | ConverterDetail::ConverterGrid grid{reader}; | |
136 |
1/2✓ Branch 1 taken 23 times.
✗ Branch 2 not taken.
|
44 | auto writer = factory(grid); |
137 |
2/4✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 23 times.
|
44 | if (reader.filename() == filename + writer.extension()) |
138 | ✗ | throw GridFormat::IOError("Cannot read/write from/to the same file"); | |
139 | if constexpr (Traits::WritesConnectivity<std::remove_cvref_t<decltype(writer)>>::value) | ||
140 |
1/2✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
|
40 | grid.make_grid(); |
141 |
1/2✓ Branch 1 taken 23 times.
✗ Branch 2 not taken.
|
88 | return ConverterDetail::write_piece(reader, writer, filename); |
142 | 44 | } | |
143 | |||
144 | /*! | ||
145 | * \ingroup Grid | ||
146 | * \brief Overload for time series formats. | ||
147 | * \param reader A grid reader on which a file was opened. | ||
148 | * \param factory A factory to construct a time series writer with the desired output format. | ||
149 | * \param call_back (optional) A callback that is invoked after writing each step. | ||
150 | */ | ||
151 | template<std::derived_from<GridReader> Reader, | ||
152 | ConverterDetail::TimeSeriesWriterFactory Factory, | ||
153 | 15 | std::invocable<std::size_t, const std::string&> StepCallBack = decltype([] (std::size_t, const std::string&) {})> | |
154 | 21 | std::string convert(Reader& reader, | |
155 | const Factory& factory, | ||
156 | const StepCallBack& call_back = {}) { | ||
157 |
2/4✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
|
21 | if (!reader.is_sequence()) |
158 | ✗ | throw ValueError("Cannot convert data from reader to a sequence as the file read is no sequence."); | |
159 | |||
160 | 21 | ConverterDetail::ConverterGrid grid{reader}; | |
161 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
21 | auto writer = factory(grid); |
162 | 21 | std::string filename; | |
163 |
3/4✓ Branch 1 taken 72 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 60 times.
✓ Branch 4 taken 12 times.
|
126 | for (std::size_t step = 0; step < reader.number_of_steps(); ++step) { |
164 |
1/2✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
|
105 | reader.set_step(step); |
165 | if constexpr (Traits::WritesConnectivity<std::remove_cvref_t<decltype(writer)>>::value) | ||
166 |
1/2✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
|
105 | grid.make_grid(); |
167 |
2/4✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 60 times.
✗ Branch 5 not taken.
|
105 | filename = ConverterDetail::write_piece(reader, writer, reader.time_at_step(step)); |
168 |
1/2✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
|
105 | call_back(step, filename); |
169 | } | ||
170 | 21 | return filename; | |
171 | 21 | } | |
172 | |||
173 | namespace Traits { | ||
174 | |||
175 | // to distinguish points/cells we use different integer types | ||
176 | |||
177 | template<> | ||
178 | struct Points<ConverterDetail::ConverterGrid> { | ||
179 | 162 | static std::ranges::range auto get(const ConverterDetail::ConverterGrid& grid) { | |
180 |
2/4✓ Branch 1 taken 162 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 162 times.
✗ Branch 5 not taken.
|
162 | return std::views::iota(std::size_t{0}, grid.reader.number_of_points()); |
181 | } | ||
182 | }; | ||
183 | |||
184 | template<> | ||
185 | struct Cells<ConverterDetail::ConverterGrid> { | ||
186 | 243 | static std::ranges::range auto get(const ConverterDetail::ConverterGrid& grid) { | |
187 |
1/2✓ Branch 1 taken 243 times.
✗ Branch 2 not taken.
|
243 | const auto max = static_cast<std::int64_t>(grid.reader.number_of_cells()); |
188 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 243 times.
|
243 | if (max < 0) |
189 | ✗ | throw TypeError("Integer overflow. Too many grid cells."); | |
190 |
1/2✓ Branch 1 taken 243 times.
✗ Branch 2 not taken.
|
243 | return std::views::iota(std::int64_t{0}, max); |
191 | } | ||
192 | }; | ||
193 | |||
194 | template<> | ||
195 | struct NumberOfPoints<ConverterDetail::ConverterGrid> { | ||
196 | 411 | static std::size_t get(const ConverterDetail::ConverterGrid& grid) { | |
197 | 411 | return grid.reader.number_of_points(); | |
198 | } | ||
199 | }; | ||
200 | |||
201 | template<> | ||
202 | struct NumberOfCells<ConverterDetail::ConverterGrid> { | ||
203 | 413 | static std::size_t get(const ConverterDetail::ConverterGrid& grid) { | |
204 | 413 | return grid.reader.number_of_cells(); | |
205 | } | ||
206 | }; | ||
207 | |||
208 | template<> | ||
209 | struct CellPoints<ConverterDetail::ConverterGrid, std::int64_t> { | ||
210 | 10380 | static std::ranges::range auto get(const ConverterDetail::ConverterGrid& grid, const std::int64_t i) { | |
211 | 10380 | return grid.cells.at(i).second | std::views::all; | |
212 | } | ||
213 | }; | ||
214 | |||
215 | template<> | ||
216 | struct CellType<ConverterDetail::ConverterGrid, std::int64_t> { | ||
217 | 10380 | static GridFormat::CellType get(const ConverterDetail::ConverterGrid& grid, const std::int64_t i) { | |
218 | 10380 | return grid.cells.at(i).first; | |
219 | } | ||
220 | }; | ||
221 | |||
222 | template<> | ||
223 | struct PointCoordinates<ConverterDetail::ConverterGrid, std::size_t> { | ||
224 | 12742 | static std::array<double, 3> get(const ConverterDetail::ConverterGrid& grid, const std::size_t i) { | |
225 | 12742 | return grid.points.at(i); | |
226 | } | ||
227 | }; | ||
228 | |||
229 | template<> | ||
230 | struct PointId<ConverterDetail::ConverterGrid, std::size_t> { | ||
231 | 70364 | static std::size_t get(const ConverterDetail::ConverterGrid&, const std::size_t i) { | |
232 | 70364 | return i; | |
233 | } | ||
234 | }; | ||
235 | |||
236 | template<> | ||
237 | struct NumberOfCellPoints<ConverterDetail::ConverterGrid, std::int64_t> { | ||
238 | 20760 | static std::size_t get(const ConverterDetail::ConverterGrid& grid, const std::int64_t i) { | |
239 | 20760 | return grid.cells.at(i).second.size(); | |
240 | } | ||
241 | }; | ||
242 | |||
243 | template<> | ||
244 | struct Origin<ConverterDetail::ConverterGrid> { | ||
245 | 2 | static std::array<double, 3> get(const ConverterDetail::ConverterGrid& grid) { | |
246 | 2 | return grid.reader.origin(); | |
247 | } | ||
248 | }; | ||
249 | |||
250 | template<> | ||
251 | struct Spacing<ConverterDetail::ConverterGrid> { | ||
252 | 2 | static std::array<double, 3> get(const ConverterDetail::ConverterGrid& grid) { | |
253 | 2 | return grid.reader.spacing(); | |
254 | } | ||
255 | }; | ||
256 | |||
257 | template<> | ||
258 | struct Basis<ConverterDetail::ConverterGrid> { | ||
259 | 2 | static std::array<std::array<double, 3>, 3> get(const ConverterDetail::ConverterGrid& grid) { | |
260 | std::array<std::array<double, 3>, 3> result; | ||
261 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
|
8 | for (unsigned int i = 0; i < 3; ++i) |
262 | 6 | result[i] = grid.reader.basis_vector(i); | |
263 | 2 | return result; | |
264 | } | ||
265 | }; | ||
266 | |||
267 | template<> | ||
268 | struct Extents<ConverterDetail::ConverterGrid> { | ||
269 | 3 | static std::array<std::size_t, 3> get(const ConverterDetail::ConverterGrid& grid) { | |
270 | 3 | return grid.reader.extents(); | |
271 | } | ||
272 | }; | ||
273 | |||
274 | template<> | ||
275 | struct Ordinates<ConverterDetail::ConverterGrid> { | ||
276 | static std::vector<double> get(const ConverterDetail::ConverterGrid& grid, unsigned int i) { | ||
277 | return grid.reader.ordinates(i); | ||
278 | } | ||
279 | }; | ||
280 | |||
281 | template<typename Entity> | ||
282 | struct Location<ConverterDetail::ConverterGrid, Entity> { | ||
283 | ✗ | static std::array<std::size_t, 3> get(const ConverterDetail::ConverterGrid& grid, const std::size_t point) { | |
284 | ✗ | return _get(Ranges::incremented(Extents<ConverterDetail::ConverterGrid>::get(grid), 1), point); | |
285 | } | ||
286 | |||
287 | ✗ | static std::array<std::size_t, 3> get(const ConverterDetail::ConverterGrid& grid, const std::int64_t cell) { | |
288 | ✗ | return _get(Extents<ConverterDetail::ConverterGrid>::get(grid), cell); | |
289 | } | ||
290 | |||
291 | private: | ||
292 | ✗ | static std::array<std::size_t, 3> _get(std::ranges::range auto extents, std::integral auto index) { | |
293 | // avoid zero extents | ||
294 | ✗ | std::ranges::for_each(extents, [] <std::integral T> (T& e) { e = std::max(e, T{1}); }); | |
295 | ✗ | const auto accumulate_until = [&] (int dim) { | |
296 | ✗ | auto range = extents | std::views::take(dim); | |
297 | ✗ | return std::accumulate( | |
298 | std::ranges::begin(range), | ||
299 | std::ranges::end(range), | ||
300 | std::size_t{1}, | ||
301 | std::multiplies{} | ||
302 | ✗ | ); | |
303 | }; | ||
304 | |||
305 | ✗ | const auto divisor1 = accumulate_until(1); | |
306 | ✗ | const auto divisor2 = accumulate_until(2); | |
307 | return { | ||
308 | ✗ | index%divisor2%divisor1, | |
309 | ✗ | index%divisor2/divisor1, | |
310 | ✗ | index/divisor2 | |
311 | ✗ | }; | |
312 | } | ||
313 | }; | ||
314 | |||
315 | } // namespace Traits | ||
316 | } // namespace GridFormat | ||
317 | |||
318 | #endif // GRIDFORMAT_GRID_CONVERTER_HPP_ | ||
319 |