OGS
writeXdmf.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Copyright (c) OpenGeoSys Community (opengeosys.org)
2// SPDX-License-Identifier: BSD-3-Clause
3
4#include "writeXdmf.h"
5
6#include <spdlog/fmt/ranges.h>
7
8#include <algorithm>
9#include <array>
10#include <fstream>
11#include <iostream>
12#include <iterator>
13#include <list>
14#include <map>
15#include <optional>
16#include <string_view>
17#include <vector>
18
19#include "BaseLib/Error.h"
20#include "BaseLib/cpp23.h"
21
22// naming convention for local function objects by postfix:
23// _transform: functions that take data (mostly XDMF meta data) and return
24// transformed data (XDMF string)
25// _fn: functions that take a function and return a new function
26
27using namespace fmt::literals;
28using namespace BaseLib;
29namespace MeshLib::IO
30{
31// similar definition in MeshEnums.h - XDMF uses capital letters
32// There is no correspondece for "IntegrationPoint" in XDMF Format
33// specification - add it as "Other"
34constexpr char const* mesh_item_type_strings[] = {"Node", "Edge", "Face",
35 "Cell", "Other"};
36
37// Transforms MeshItemType into string
38static std::string meshItemTypeString(
39 std::optional<MeshItemType> const& item_type)
40{
41 if (item_type)
42 {
43 return mesh_item_type_strings[static_cast<int>(item_type.value())];
44 }
45 OGS_FATAL("Cannot convert an empty optional mesh item type.");
46}
47
48// Transforms MeshPropertyDataType into string
49// constexpr with literal type std::string not yet supported
51{
52 std::array<std::string, to_underlying(MeshPropertyDataType::enum_length)>
53 ogs_to_xdmf_type = {};
54 ogs_to_xdmf_type[to_underlying(MeshPropertyDataType::float64)] = "Float";
55 ogs_to_xdmf_type[to_underlying(MeshPropertyDataType::float32)] = "Float";
56 ogs_to_xdmf_type[to_underlying(MeshPropertyDataType::int32)] = "Int";
57 ogs_to_xdmf_type[to_underlying(MeshPropertyDataType::int64)] = "Int";
58 ogs_to_xdmf_type[to_underlying(MeshPropertyDataType::uint32)] = "UInt";
59 ogs_to_xdmf_type[to_underlying(MeshPropertyDataType::uint64)] = "UInt";
60 ogs_to_xdmf_type[to_underlying(MeshPropertyDataType::int8)] = "Int";
61 ogs_to_xdmf_type[to_underlying(MeshPropertyDataType::uint8)] = "UInt";
62 ogs_to_xdmf_type[to_underlying(MeshPropertyDataType::char_native)] = "Char";
63 ogs_to_xdmf_type[to_underlying(MeshPropertyDataType::uchar)] = "UChar";
64 return ogs_to_xdmf_type;
65}
66
67// Transform MeshPropertyDatatype into string.
68static std::string getPropertyDataTypeString(
69 MeshPropertyDataType const& ogs_data_type)
70{
71 return meshPropertyDatatypeString()[to_underlying(ogs_data_type)];
72}
73
74// Returns MeshPropertyDataType-to-Size (in bytes) lookup-table
75static constexpr auto meshPropertyDatatypeSize()
76{
78 property_sizes{};
81 property_sizes[to_underlying(MeshPropertyDataType::int32)] = 4,
82 property_sizes[to_underlying(MeshPropertyDataType::int64)] = 8,
85 property_sizes[to_underlying(MeshPropertyDataType::int8)] = 1,
86 property_sizes[to_underlying(MeshPropertyDataType::uint8)] = 1;
87 return property_sizes;
88}
89
91
92// Transform MeshPropertyDataType into type_sizes (in bytes)
93static std::string getPropertyDataTypeSize(
94 MeshPropertyDataType const& ogs_data_type)
95{
96 return fmt::format("{}", ogs_to_xdmf_type_fn[to_underlying(ogs_data_type)]);
97}
98
99// Collects all known data and return a function that takes all unknown data
100// known: XdmfData of mesh to be written, unknown: timesteps
101std::function<std::string(std::vector<double>)> write_xdmf(
102 XdmfData const& geometry, XdmfData const& topology,
103 std::vector<XdmfData> const& constant_attributes,
104 std::vector<XdmfData> const& variable_attributes,
105 std::string const& h5filename, std::string const& ogs_version,
106 std::string const& mesh_name)
107{
108 // Generates function that writes <DataItem>. Late binding needed because
109 // maximum number of steps unknown. Time step and h5filename are captured to
110 // return a unary function
111 // _a suffix is a user defined literal for fmt named arguments
112 auto const time_dataitem_genfn =
113 [](unsigned long long const time_step, int const max_step,
114 std::string const& h5filename, std::string const& mesh_name)
115 {
116 return
117 [time_step, max_step, h5filename, mesh_name](auto const& xdmfdata)
118 {
119 return fmt::format(
120 fmt::runtime("\n\t\t<DataItem DataType=\"{datatype}\" "
121 "Dimensions=\"{local_dimensions}\" "
122 "Format=\"HDF\" "
123 "Precision=\"{precision}\">"
124 "{filename}:/meshes/{meshname}/{datasetname}|"
125 "{time_step} {starts}:1 {strides}:1 "
126 "{local_dimensions}:{max_step} "
127 "{global_dimensions}</"
128 "DataItem>"),
129 "datatype"_a = getPropertyDataTypeString(xdmfdata.data_type),
130 "local_dimensions"_a =
131 fmt::join(xdmfdata.global_block_dims, " "),
132 "precision"_a = getPropertyDataTypeSize(xdmfdata.data_type),
133 "filename"_a = h5filename,
134 "meshname"_a = mesh_name,
135 "datasetname"_a = xdmfdata.name,
136 "starts"_a = fmt::join(xdmfdata.starts, " "),
137 "strides"_a = fmt::join(xdmfdata.strides, " "),
138 "global_dimensions"_a =
139 fmt::join(xdmfdata.global_block_dims, " "),
140 "time_step"_a = fmt::format("{}", time_step),
141 "max_step"_a = fmt::format("{}", max_step));
142 };
143 };
144
145 // string_join could be moved to ogs lib if used more
146 auto const string_join_fn = [](auto const& collection)
147 { return fmt::to_string(fmt::join(collection, "")); };
148
149 // m_bind could be moved to ogs lib if used more
150 // Uses indexed transform - transform receives (element, index)
151 auto const m_bind_fn = [](auto const& transform, auto const& join)
152 {
153 return [join, transform](auto const& collection)
154 {
155 std::vector<std::string> temp;
156 temp.reserve(collection.size());
157 for (std::size_t i = 0; i < collection.size(); ++i)
158 {
159 // 1 - TimeStep , 2 Geometry, 3 Topology, 4-x Attributes
160 temp.push_back(transform(collection[i], i + 4));
161 }
162 return join(temp);
163 };
164 };
165
166 // XDMF if part of the data that is already written in any previous step
167 // subsequent steps can refer to this data collection of elements navigates
168 // the xml tree (take first child, then in child take 2nd child ...)
169 auto const pointer_transfrom = [](auto const& elements)
170 {
171 return fmt::format(
172 fmt::runtime("\n\t<xi:include xpointer=\"element(/{elements})\"/>"),
173 "elements"_a = fmt::join(elements, "/"));
174 };
175
176 // Defines content of <Attribute> in XDMF, child of Attribute is a single
177 // <DataItem>
178 auto const attribute_transform =
179 [](XdmfData const& attribute, auto const& dataitem_transform)
180 {
181 return fmt::format(
182 "\n\t<Attribute Center=\"{center}\" ElementCell=\"\" "
183 "ElementDegree=\"0\" "
184 "ElementFamily=\"\" ItemType=\"\" Name=\"{name}\" "
185 "Type=\"None\">{dataitem}\n\t</Attribute>",
186 "center"_a = meshItemTypeString(attribute.attribute_center),
187 "name"_a = attribute.name,
188 "dataitem"_a = dataitem_transform(attribute));
189 };
190
191 // Define content of <Geometry> in XDMF, same as attribute_transform
192 auto const geometry_transform =
193 [](XdmfData const& geometry, auto const& dataitem_transform)
194 {
195 return fmt::format(
196 fmt::runtime("\n\t<Geometry Origin=\"\" "
197 "Type=\"XYZ\">{dataitem}\n\t</Geometry>"),
198 "dataitem"_a = dataitem_transform(geometry));
199 };
200
201 auto tag_string = [](auto const& topology, int nodes_per_element,
202 auto const& dataitem_transform)
203 {
204 return fmt::format(
205 fmt::runtime("\n\t<Topology Type=\"{topology_type}\" "
206 "NodesPerElement=\"{nodes_per_element}\" "
207 "NumberOfElements=\"{number_of_elements}\">{dataitem}"
208 "\n\t</Topology>"),
209 "topology_type"_a =
211 "dataitem"_a = dataitem_transform(topology),
212 "nodes_per_element"_a = nodes_per_element,
213 "number_of_elements"_a = topology.size_partitioned_dim);
214 };
215
216 // Define content of <Topology> in XDMF, same as attribute_transform
217 auto const topology_transform =
218 [&tag_string](XdmfData const& topology, auto const& dataitem_transform)
219 {
220 switch (*topology.parent_data_type)
221 {
223 return tag_string(topology, 1, dataitem_transform);
225 return tag_string(topology, 2, dataitem_transform);
243 return fmt::format(
244 fmt::runtime(
245 "\n\t<Topology "
246 "Type=\"{topology_type}\">{dataitem}\n\t</Topology>"),
247 "topology_type"_a =
249 "dataitem"_a = dataitem_transform(topology));
250 }
251 OGS_FATAL("Could not transform unknown XDMF topology type");
252 return std::string{};
253 };
254
255 // Defines content of <Grid> of a single time step, takes specific transform
256 // functions for all of its child elements
257 auto const grid_transform =
258 [](double const& time_value, auto const& geometry, auto const& topology,
259 auto const& constant_attributes, auto const& variable_attributes)
260 {
261 return fmt::format(
262 R"(
263<Grid Name="Grid" GridType="Uniform">
264 <Time Value="{time_value:.{precision}g}"/>
265{geometry}
266{topology}
267{fix_attributes}
268{variable_attributes}
269</Grid>)",
270 "time_value"_a = time_value,
271 // Output of "Time Value" with sufficient precision.
272 "precision"_a = std::numeric_limits<double>::max_digits10,
273 "geometry"_a = geometry,
274 "topology"_a = topology,
275 "fix_attributes"_a = constant_attributes,
276 "variable_attributes"_a = variable_attributes);
277 };
278
279 // An attribute may change over time (variable) or stay constant
280 enum class time_attribute
281 {
282 constant,
283 variable
284 };
285
286 // Generates a function that either writes the data or points to existing
287 // data
288 auto const time_step_fn = [time_dataitem_genfn, pointer_transfrom,
289 h5filename,
290 mesh_name](auto const& component_transform,
291 unsigned long long const time_step,
292 int const max_step,
293 time_attribute const variable)
294 {
295 return [component_transform, time_dataitem_genfn, pointer_transfrom,
296 variable, time_step, max_step, h5filename,
297 mesh_name](XdmfData const& attr, std::size_t const index)
298 {
299 // new data arrived
300 bool const changed =
301 ((time_step > 0 && (variable == time_attribute::variable)) ||
302 time_step == 0);
303 if (changed)
304 {
305 auto dataitem = time_dataitem_genfn(time_step, max_step,
306 h5filename, mesh_name);
307 return component_transform(attr, dataitem);
308 }
309 else
310 {
311 // index + 3 0 Time, 1 Geometry 2 Topology
312 std::array<unsigned int, 5> position = {
313 1, 1, 2, 1, static_cast<unsigned int>(index)};
314 return pointer_transfrom(position);
315 };
316 };
317 };
318
319 // Top-Level transform function (take spatial and temporal transform
320 // functions) and return the time depended grid transform function
321 // ToDo (tm) Remove capturing m_bind and string_join as helper function
322
323 auto const time_grid_transform =
324 [time_step_fn, m_bind_fn, string_join_fn, grid_transform,
325 geometry_transform, topology_transform, attribute_transform](
326 double const time, int const step, int const max_step,
327 auto const& geometry, auto const& topology,
328 auto const& constant_attributes, auto const& variable_attributes)
329 {
330 auto const time_step_geometry_transform = time_step_fn(
331 geometry_transform, step, max_step, time_attribute::constant);
332 auto const time_step_topology_transform = time_step_fn(
333 topology_transform, step, max_step, time_attribute::constant);
334 auto const time_step_const_attribute_fn = time_step_fn(
335 attribute_transform, step, max_step, time_attribute::constant);
336 auto const time_step_variable_attribute_fn = time_step_fn(
337 attribute_transform, step, max_step, time_attribute::variable);
338
339 // lift both functions from handling single attributes to work with
340 // collection of attributes
341 auto const variable_attributes_transform =
342 m_bind_fn(time_step_variable_attribute_fn, string_join_fn);
343 auto const constant_attributes_transform =
344 m_bind_fn(time_step_const_attribute_fn, string_join_fn);
345
346 return grid_transform(
347 time, time_step_geometry_transform(geometry, 2),
348 time_step_topology_transform(topology, 3),
349 constant_attributes_transform(constant_attributes),
350 variable_attributes_transform(variable_attributes));
351 };
352
353 // Function that combines all the data from spatial (geometry, topology,
354 // attribute) and temporal domain (time) with the time domain
355 // (time_step_fn). And writes <Grid CollectionType="Temporal">
356 auto const temporal_grid_collection_transform =
357 [time_grid_transform](
358 auto const& times, auto const& geometry, auto const& topology,
359 auto const& constant_attributes, auto const& variable_attributes)
360 {
361 // c++20 ranges zip missing
362 auto const max_step = times.size();
363 std::vector<std::string> grids;
364 grids.reserve(max_step);
365 for (size_t time_step = 0; time_step < max_step; ++time_step)
366 {
367 grids.push_back(time_grid_transform(
368 times[time_step], time_step, max_step, geometry, topology,
369 constant_attributes, variable_attributes));
370 }
371 return fmt::format(
372 fmt::runtime(
373 "\n<Grid CollectionType=\"Temporal\" GridType=\"Collection\" "
374 "Name=\"Collection\">{grids}\n</Grid>\n"),
375 "grids"_a = fmt::join(grids, ""));
376 };
377
378 // Generator function that return a function that represents complete xdmf
379 // structure
380 auto const xdmf_writer_fn = [temporal_grid_collection_transform](
381 auto ogs_version, auto geometry,
382 auto topology, auto constant_attributes,
383 auto variable_attributes)
384 {
385 // This function will be the return of the surrounding named function
386 // capture by copy - it is the function that will survive this scope!!
387 // Use generalized lambda capture to move for optimization
388 return [temporal_grid_collection_transform,
389 ogs_version = std::move(ogs_version),
390 geometry = std::move(geometry), topology = std::move(topology),
391 constant_attributes = std::move(constant_attributes),
392 variable_attributes = std::move(variable_attributes)](
393 std::vector<double> const& times)
394 {
395 return fmt::format(
396 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Xdmf "
397 "xmlns:xi=\"http://www.w3.org/2001/XInclude\" "
398 "Version=\"3.0\">\n<Domain>\n<Information Name=\"OGS_VERSION\" "
399 "Value=\"{ogs_version}\"/>{grid_collection}</Domain>\n</Xdmf>",
400 "ogs_version"_a = ogs_version,
401 "grid_collection"_a = temporal_grid_collection_transform(
402 times, geometry, topology, constant_attributes,
403 variable_attributes));
404 };
405 };
406
407 return xdmf_writer_fn(ogs_version, geometry, topology, constant_attributes,
408 variable_attributes);
409}
410} // namespace MeshLib::IO
#define OGS_FATAL(...)
Definition Error.h:19
std::pair< std::string, std::size_t > ParentDataType2String(ParentDataType p)
MeshPropertyDataType
constexpr auto to_underlying(E e) noexcept
Converts an enumeration to its underlying type.
Definition cpp23.h:22
static std::string getPropertyDataTypeString(MeshPropertyDataType const &ogs_data_type)
Definition writeXdmf.cpp:68
static auto meshPropertyDatatypeString()
Definition writeXdmf.cpp:50
static std::string meshItemTypeString(std::optional< MeshItemType > const &item_type)
Definition writeXdmf.cpp:38
constexpr char const * mesh_item_type_strings[]
Definition writeXdmf.cpp:34
static std::string getPropertyDataTypeSize(MeshPropertyDataType const &ogs_data_type)
Definition writeXdmf.cpp:93
std::function< std::string(std::vector< double >)> write_xdmf(XdmfData const &geometry, XdmfData const &topology, std::vector< XdmfData > const &constant_attributes, std::vector< XdmfData > const &variable_attributes, std::string const &h5filename, std::string const &ogs_version, std::string const &mesh_name)
Generator function that creates a function capturing the spatial data of a mesh Temporal data can lat...
auto ogs_to_xdmf_type_fn
Definition writeXdmf.cpp:90
static constexpr auto meshPropertyDatatypeSize()
Definition writeXdmf.cpp:75
std::optional< MeshLib::MeshItemType > attribute_center
Definition XdmfData.h:55
std::size_t size_partitioned_dim
Definition XdmfData.h:53
std::string name
Definition XdmfData.h:54
std::optional< ParentDataType > parent_data_type
Definition XdmfData.h:56