Arcane  4.2.3.0
User documentation
Loading...
Searching...
No Matches
VtkHdfV2PostProcessor.cc
1// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
2//-----------------------------------------------------------------------------
3// Copyright 2000-2026 CEA (www.cea.fr) IFPEN (www.ifpenergiesnouvelles.com)
4// See the top-level COPYRIGHT file for details.
5// SPDX-License-Identifier: Apache-2.0
6//-----------------------------------------------------------------------------
7/*---------------------------------------------------------------------------*/
8/* VtkHdfV2PostProcessor.cc (C) 2000-2026 */
9/* */
10/* Post-processing in VTK HDF format. */
11/*---------------------------------------------------------------------------*/
12/*---------------------------------------------------------------------------*/
13
14#include "arcane/utils/Collection.h"
15#include "arcane/utils/Enumerator.h"
16#include "arcane/utils/Iostream.h"
17#include "arcane/utils/StringBuilder.h"
18#include "arcane/utils/IOException.h"
19#include "arcane/utils/FixedArray.h"
20#include "arcane/utils/MemoryView.h"
21
22#include "arcane/core/PostProcessorWriterBase.h"
23#include "arcane/core/Directory.h"
24#include "arcane/core/FactoryService.h"
25#include "arcane/core/IDataWriter.h"
26#include "arcane/core/IData.h"
27#include "arcane/core/IItemFamily.h"
28#include "arcane/core/MeshKind.h"
29#include "arcane/core/VariableCollection.h"
30#include "arcane/core/IParallelMng.h"
31#include "arcane/core/IMesh.h"
32#include "arcane/core/internal/IParallelMngInternal.h"
33#include "arcane/core/internal/VtkCellTypes.h"
34#include "arcane/core/internal/GatherGroup.h"
35
36#include "arcane/core/materials/IMeshMaterialMng.h"
37#include "arcane/core/materials/IMeshEnvironment.h"
38
39#include "arcane/hdf5/Hdf5Utils.h"
40#include "arcane/hdf5/VtkHdfV2PostProcessor_axl.h"
41
42#include <map>
43
44// This format is described on the following web page:
45//
46// https://kitware.github.io/vtk-examples/site/VTKFileFormats/#hdf-file-formats
47//
48// Format 2.0 with integrated temporal evolution support is only available
49// in the VTK master branch starting from April 2023.
50
51/*---------------------------------------------------------------------------*/
52/*---------------------------------------------------------------------------*/
53
54// TODO: Add verification test for saved values
55
56// TODO: Look into saving uniqueId() (via vtkOriginalCellIds)
57
58// TODO: Look into how to avoid saving the mesh at every iteration if it
59// does not change.
60
61// TODO: Look into compression
62
63// TODO: handle 2D variables
64
65// TODO: outside of HDF5, implement a mechanism that groups several parts
66// of the cell into one. This will allow reducing the number of ghost cells
67// and using MPI/IO in hybrid mode.
68
69/*---------------------------------------------------------------------------*/
70/*---------------------------------------------------------------------------*/
71
72namespace Arcane
73{
74using namespace Hdf5Utils;
75using namespace Materials;
76
77namespace
78{
79 template <typename T> Span<const T>
80 asConstSpan(const T* v)
81 {
82 return Span<const T>(v, 1);
83 }
84} // namespace
85
86/*---------------------------------------------------------------------------*/
87/*---------------------------------------------------------------------------*/
88
89class VtkHdfV2DataWriter
90: public TraceAccessor
91, public IDataWriter
92{
93 public:
94
95 /*!
96 * \brief Class to store a pair (hdf_group, dataset_name).
97 *
98 * Instances of this class use a reference to an HDF5 group
99 * and thus this group must live longer than the instance.
100 */
101 struct DatasetGroupAndName
102 {
103 public:
104
105 DatasetGroupAndName(HGroup& group_, const String& name_)
106 : group(group_)
107 , name(name_)
108 {}
109
110 public:
111
112 HGroup& group;
113 String name;
114 };
115
116 /*!
117 * \brief Class to store offset information.
118 *
119 * This is a pair (hdf_group, dataset_name).
120 *
121 * The group can be null in which case it is an offset that is
122 * only calculated and will not be saved.
123 *
124 * Instances of this class use a reference to an HDF5 group
125 * and thus this group must live longer than the instance.
126 */
127 struct DatasetInfo
128 {
129 DatasetInfo() = default;
130 explicit DatasetInfo(const String& name)
131 : m_name(name)
132 {}
133 DatasetInfo(HGroup& _group, const String& name)
134 : m_group(&_group)
135 , m_name(name)
136 {}
137 bool isNull() const { return m_name.null(); }
138
139 HGroup* group() const { return m_group; }
140 const String& name() const { return m_name; }
141 //! Offset value. (-1) if writing to the end of the array
142 Int64 offset() const { return m_offset; }
143 void setOffset(Int64 v) { m_offset = v; }
144 friend bool operator<(const DatasetInfo& s1, const DatasetInfo& s2)
145 {
146 return (s1.m_name < s2.m_name);
147 }
148
149 private:
150
151 HGroup* m_group = nullptr;
152 String m_name;
153 Int64 m_offset = -1;
154 };
155
156 //! Offset information for the part to write associated with a rank
158 {
159 public:
160
161 void setTotalSize(Int64 v) { m_total_size = v; }
162 void setSize(Int64 v) { m_size = v; }
163 void setOffset(Int64 v) { m_offset = v; }
164
165 Int64 totalSize() const { return m_total_size; }
166 Int64 size() const { return m_size; }
167 Int64 offset() const { return m_offset; }
168
169 private:
170
171 //! Number of elements across all ranks
172 Int64 m_total_size = 0;
173 //! Number of elements on my rank
174 Int64 m_size = 0;
175 //! Offset of my rank
176 Int64 m_offset = -1;
177 };
178
179 //! Collective information for an ItemGroup;
180 struct ItemGroupCollectiveInfo
181 {
182 public:
183
184 explicit ItemGroupCollectiveInfo(const ItemGroup& g)
185 : m_item_group(g)
186 {}
187
188 public:
189
190 void setWritePartInfo(const WritePartInfo& part_info) { m_write_part_info = part_info; }
191 const WritePartInfo& writePartInfo() const { return m_write_part_info; }
192
193 public:
194
195 //! Associated group
197 //! Writing information.
199 };
200
201 /*!
202 * \brief Stores info about the data to be saved and the associated offset.
203 */
204 struct DataInfo
205 {
206 public:
207
208 DataInfo(const DatasetGroupAndName& dname, const DatasetInfo& dataset_info)
209 : dataset(dname)
210 , m_dataset_info(dataset_info)
211 {
212 }
213 DataInfo(const DatasetGroupAndName& dname, const DatasetInfo& dataset_info,
214 ItemGroupCollectiveInfo* group_info)
215 : dataset(dname)
216 , m_dataset_info(dataset_info)
217 , m_group_info(group_info)
218 {
219 }
220 DataInfo(HGroup& dataset_group, const String& dataset_name, const DatasetInfo& dataset_info)
221 : dataset(dataset_group, dataset_name)
222 , m_dataset_info(dataset_info)
223 {
224 }
225 DataInfo(HGroup& dataset_group, const String& dataset_name, const DatasetInfo& dataset_info,
226 ItemGroupCollectiveInfo* group_info)
227 : dataset(dataset_group, dataset_name)
228 , m_dataset_info(dataset_info)
229 , m_group_info(group_info)
230 {
231 }
232
233 public:
234
235 DatasetInfo datasetInfo() const { return m_dataset_info; }
236
237 public:
238
239 DatasetGroupAndName dataset;
240 DatasetInfo m_dataset_info;
241 ItemGroupCollectiveInfo* m_group_info = nullptr;
242 };
243
244 public:
245
246 VtkHdfV2DataWriter(IMesh* mesh, const ItemGroupCollection& groups, bool is_collective_io);
247
248 public:
249
250 void beginWrite(const VariableCollection& vars) override;
251 void endWrite() override;
252 void setMetaData(const String& meta_data) override;
253 void write(IVariable* var, IData* data) override;
254
255 public:
256
257 void setTimes(RealConstArrayView times) { m_times = times; }
258 void setDirectoryName(const String& dir_name) { m_directory_name = dir_name; }
259 void setMaxWriteSize(Int64 v) { m_max_write_size = v; }
260
261 private:
262
263 //! Associated mesh
264 IMesh* m_mesh = nullptr;
265
266 //! Associated material manager (may be null)
267 IMeshMaterialMng* m_material_mng = nullptr;
268
269 //! List of groups to save
270 ItemGroupCollection m_groups;
271
272 //! List of times
273 UniqueArray<Real> m_times;
274
275 //! Current HDF filename
276 String m_full_filename;
277
278 //! Output directory.
279 String m_directory_name;
280
281 //! HDF file identifier
282 HFile m_file_id;
283
284 HGroup m_top_group;
285 HGroup m_cell_data_group;
286 HGroup m_node_data_group;
287
288 HGroup m_steps_group;
289 HGroup m_point_data_offsets_group;
290 HGroup m_cell_data_offsets_group;
291 HGroup m_field_data_offsets_group;
292
293 bool m_is_parallel = false;
294 bool m_is_collective_io = false;
295 bool m_is_first_call = false;
296 bool m_is_writer = false;
297
298 /*!
299 * \brief True if the datasets related to the polyhedron cells
300 * must be written for the current time step.
301 *
302 * This is true either when the mesh contains at least one polyhedron
303 * cell or when these datasets have already been created for a previous
304 * time step.
305 */
306 bool m_write_polyhedron_datasets = false;
307
308 Int32 m_writer = 0;
309
310 DatasetInfo m_cell_offset_info;
311 DatasetInfo m_point_offset_info;
312 DatasetInfo m_connectivity_offset_info;
313 DatasetInfo m_face_connectivity_offset_info;
314 DatasetInfo m_polyhedron_to_face_offset_info;
315 DatasetInfo m_face_offsets_offset_info;
316 DatasetInfo m_offset_for_cell_offset_info;
317 DatasetInfo m_offset_for_face_offset_info;
318 DatasetInfo m_offset_for_polyhedron_offset_info;
319 DatasetInfo m_part_offset_info;
320 DatasetInfo m_time_offset_info;
321 std::map<DatasetInfo, Int64> m_offset_info_list;
322
323 StandardTypes m_standard_types{ false };
324
325 ItemGroupCollectiveInfo m_all_cells_info;
326 ItemGroupCollectiveInfo m_all_nodes_info;
327 UniqueArray<Ref<ItemGroupCollectiveInfo>> m_materials_groups;
328
329 GatherGroupInfo m_all_cells_gather_group_info;
330 GatherGroupInfo m_all_nodes_gather_group_info;
331 UniqueArray<Ref<GatherGroupInfo>> m_gather_info_materials_groups;
332
333 /*!
334 * \brief Maximum size (in kilobytes) for a write operation.
335 *
336 * If the write exceeds this size, it is split into multiple writes.
337 * This may be necessary with MPI-IO for large volumes.
338 */
339 Int64 m_max_write_size = 0;
340
341 private:
342
343 void _addInt64ArrayAttribute(Hid& hid, const char* name, Span<const Int64> values);
344 void _addStringAttribute(Hid& hid, const char* name, const String& value);
345
346 template <typename DataType> void
347 _writeDataSet1D(const DataInfo& data_info, GatherGroupInfo* gather_info, Span<const DataType> values);
348 template <typename DataType> void
349 _writeDataSet1DUsingCollectiveIO(const DataInfo& data_info, GatherGroupInfo* gather_info, Span<const DataType> values);
350 template <typename DataType> void
351 _writeDataSet1DCollective(const DataInfo& data_info, GatherGroupInfo* gather_info, Span<const DataType> values);
352 template <typename DataType> void
353 _writeDataSet2D(const DataInfo& data_info, GatherGroupInfo* gather_info, Span2<const DataType> values);
354 template <typename DataType> void
355 _writeDataSet2DUsingCollectiveIO(const DataInfo& data_info, GatherGroupInfo* gather_info, Span2<const DataType> values);
356 template <typename DataType> void
357 _writeDataSet2DCollective(const DataInfo& data_info, GatherGroupInfo* gather_info, Span2<const DataType> values);
358 template <typename DataType> void
359 _writeBasicTypeDataset(const DataInfo& data_info, GatherGroupInfo* gather_info, IData* data);
360 void _writeReal3Dataset(const DataInfo& data_info, GatherGroupInfo* gather_info, IData* data);
361 void _writeReal2Dataset(const DataInfo& data_info, GatherGroupInfo* gather_info, IData* data);
362
363 String _getFileName()
364 {
365 StringBuilder sb(m_mesh->name());
366 sb += ".hdf";
367 return sb.toString();
368 }
369 template <typename DataType> void
370 _writeDataSetGeneric(const DataInfo& data_info, GatherGroupInfo* gather_info, Int32 nb_dim,
371 Int64 dim1_size, Int64 dim2_size, const DataType* values_data,
372 bool is_collective);
373 void _writeDataSetGeneric(const DataInfo& data_info, GatherGroupInfo* gather_info, Int32 nb_dim,
374 Int64 dim1_size, Int64 dim2_size, ConstMemoryView values_data,
375 const hid_t hdf_datatype_type, bool is_collective);
376 void _addInt64Attribute(Hid& hid, const char* name, Int64 value);
377 Int64 _readInt64Attribute(Hid& hid, const char* name);
378 void _openOrCreateGroups();
379 void _closeGroups();
380 void _readAndSetOffset(DatasetInfo& offset_info, Int32 wanted_step);
381 void _initializeOffsets();
382 void _initializeItemGroupCollectiveInfos(ItemGroupCollectiveInfo& group_info, GatherGroupInfo& gather_info);
383 WritePartInfo _computeWritePartInfo(Int64 local_size);
384 void _writeConstituentsGroups();
385};
386
387/*---------------------------------------------------------------------------*/
388/*---------------------------------------------------------------------------*/
389
390VtkHdfV2DataWriter::
391VtkHdfV2DataWriter(IMesh* mesh, const ItemGroupCollection& groups, bool is_collective_io)
392: TraceAccessor(mesh->traceMng())
393, m_mesh(mesh)
394, m_groups(groups)
395, m_is_collective_io(is_collective_io)
396, m_all_cells_info(mesh->allCells())
397, m_all_nodes_info(mesh->allNodes())
398, m_all_cells_gather_group_info(mesh->parallelMng(), is_collective_io)
399, m_all_nodes_gather_group_info(mesh->parallelMng(), is_collective_io)
400{
401}
402
403/*---------------------------------------------------------------------------*/
404/*---------------------------------------------------------------------------*/
405
406void VtkHdfV2DataWriter::
407beginWrite(const VariableCollection& vars)
408{
409 ARCANE_UNUSED(vars);
410
411 // Retrieve the material manager if it exists
412 m_material_mng = IMeshMaterialMng::getReference(m_mesh, false);
413
414 IParallelMng* pm = m_mesh->parallelMng();
415 const Int32 nb_rank = pm->commSize();
416 m_is_parallel = nb_rank > 1;
417
418 Int32 time_index = m_times.size();
419 const bool is_first_call = (time_index < 2);
420 m_is_first_call = is_first_call;
421 if (is_first_call)
422 info() << "WARNING: The 'VtkHdfV2' implementation is experimental";
423
424 String filename = _getFileName();
425
426 Directory dir(m_directory_name);
427
428 m_full_filename = dir.file(filename);
429 info(4) << "VtkHdfV2DataWriter::beginWrite() file=" << m_full_filename;
430
431 HInit();
432 HInit::useMutex(pm->isThreadImplementation(), pm);
433
434 // It is possible to use the collective mode of HDF5 via MPI-IO in the following cases:
435 // * Hdf5 was compiled with MPI,
436 // * we are in pure MPI mode (neither shared memory mode nor hybrid mode).
437 m_is_collective_io = m_is_collective_io && (pm->isParallel() && HInit::hasParallelHdf5());
438 if (pm->isThreadImplementation() && !pm->isHybridImplementation())
439 m_is_collective_io = false;
440
441 if (is_first_call) {
442 info() << "VtkHdfV2DataWriter: using collective MPI/IO ?=" << m_is_collective_io;
443 info() << "VtkHdfV2DataWriter: max_write_size (kB) =" << m_max_write_size;
444 info() << "VtkHdfV2DataWriter: has_material?=" << (m_material_mng != nullptr);
445 }
446
447 bool is_master_io = pm->isMasterIO();
448
449 // True if we must participate in the writes
450 // If we use MPI/IO with HDF5, all
451 // ranks must perform all write operations to guarantee
452 // metadata consistency.
453 if (m_is_collective_io) {
454 m_writer = pm->_internalApi()->masterParallelIORank();
455 m_is_writer = (m_writer == pm->commRank());
456 }
457 else {
458 m_writer = pm->masterIORank();
459 m_is_writer = is_master_io;
460 }
461
462 // Indicates that we are using MPI/IO if requested
463 HProperty plist_id;
464 if (m_is_collective_io && m_is_writer)
465 plist_id.createFilePropertyMPIIO(pm);
466
467 // Even with MPI-IO, only one proc must create the directory.
468 if (is_first_call && is_master_io)
469 dir.createDirectory();
470
471 if (m_is_collective_io)
472 pm->barrier();
473
474 if (m_is_writer) {
475 m_standard_types.initialize();
476
477 if (is_first_call)
478 m_file_id.openTruncate(m_full_filename, plist_id.id());
479 else
480 m_file_id.openAppend(m_full_filename, plist_id.id());
481
482 _openOrCreateGroups();
483
484 if (is_first_call) {
485 std::array<Int64, 2> version = { 2, 5 };
486 _addInt64ArrayAttribute(m_top_group, "Version", version);
487 _addStringAttribute(m_top_group, "Type", "UnstructuredGrid");
488 }
489 }
490
491 // Initializes collective information on cell and node groups
492 _initializeItemGroupCollectiveInfos(m_all_cells_info, m_all_cells_gather_group_info);
493 _initializeItemGroupCollectiveInfos(m_all_nodes_info, m_all_nodes_gather_group_info);
494
495 CellGroup all_cells = m_mesh->allCells();
496 NodeGroup all_nodes = m_mesh->allNodes();
497
498 const Int32 nb_cell = all_cells.size();
499 const Int32 nb_node = all_nodes.size();
500
501 Int64 total_nb_connected_node = 0;
502 ENUMERATE_ (Cell, icell, all_cells) {
503 Cell cell = *icell;
504 total_nb_connected_node += cell.nodeIds().size();
505 }
506
507 // For the offsets, the array size is equal
508 // to the number of cells plus 1.
509 UniqueArray<Int64> cells_connectivity(total_nb_connected_node);
510 UniqueArray<Int64> cells_offset(nb_cell + 1);
511 UniqueArray<unsigned char> cells_ghost_type(nb_cell);
512 UniqueArray<unsigned char> cells_type(nb_cell);
513 UniqueArray<Int64> cells_uid(nb_cell);
514
515 // Arrays for the polyhedron cells.
516 //
517 // The 'faces_connectivity' array contains the list of nodes of each face
518 // of each polyhedron cell and 'faces_offset' contains the offset of each
519 // face in 'faces_connectivity' (its size is equal to the number of faces
520 // plus 1). Each polyhedron cell has its own list of faces (a face shared
521 // by two cells is saved twice because VTK requires that the nodes of a
522 // face are ordered so that the face normal points outwards from the cell).
523 //
524 // The 'polyhedron_to_faces' array contains for each cell the list of its
525 // faces (this list is empty for cells that are not polyhedron cells) and
526 // 'polyhedron_offset' contains the offset of each cell in
527 // 'polyhedron_to_faces' (its size is equal to the number of cells plus 1).
528 UniqueArray<Int64> faces_connectivity;
529 UniqueArray<Int64> faces_offset;
530 UniqueArray<Int64> polyhedron_to_faces;
531 UniqueArray<Int64> polyhedron_offset(nb_cell + 1);
532 faces_offset.add(0);
533 polyhedron_offset[0] = 0;
534 Int64 nb_face = 0;
535
536 // In a polyhedral mesh, all the cells with at least one face are
537 // considered as polyhedron cells. For the other meshes, only the cells
538 // with a polyhedron type are considered as polyhedron cells.
539 const bool mesh_is_polyhedral = (m_mesh->meshKind().meshStructure() == eMeshStructure::Polyhedral);
540
541 cells_offset[0] = 0;
542 {
543 Int32 connected_node_index = 0;
544 ENUMERATE_CELL (icell, all_cells) {
545 Int32 index = icell.index();
546 Cell cell = *icell;
547
548 cells_uid[index] = cell.uniqueId();
549
550 Byte ghost_type = 0;
551 bool is_ghost = !cell.isOwn();
552 if (is_ghost)
553 ghost_type = VtkUtils::CellGhostTypes::DUPLICATECELL;
554 cells_ghost_type[index] = ghost_type;
555
556 const bool is_polyhedron = cell.typeInfo()->isPolyhedron() || (mesh_is_polyhedral && cell.nbFace() > 0);
557 unsigned char vtk_type = VtkUtils::VTK_POLYHEDRON;
558 if (!is_polyhedron)
559 vtk_type = VtkUtils::arcaneToVtkCellType(cell.type());
560 cells_type[index] = vtk_type;
561 for (NodeLocalId node : cell.nodeIds()) {
562 cells_connectivity[connected_node_index] = node;
563 ++connected_node_index;
564 }
565 cells_offset[index + 1] = connected_node_index;
566
567 if (is_polyhedron) {
568 for (Face face : cell.faces()) {
569 // VTK requires that the nodes of a polyhedron face are ordered so
570 // that the normal to that face points outwards from the cell. In
571 // Arcane, the stored orientation of a face is outwards for its
572 // back cell. For the front cell, the order of the nodes must
573 // therefore be reversed.
574 Int32 face_nb_node = face.nbNode();
575 bool is_back_cell = (face.backCellId().localId() == cell.localId());
576 if (is_back_cell) {
577 for (NodeLocalId node : face.nodeIds())
578 faces_connectivity.add(node);
579 }
580 else {
581 NodeLocalIdView node_ids = face.nodeIds();
582 for (Int32 i = face_nb_node - 1; i >= 0; --i)
583 faces_connectivity.add(node_ids[i]);
584 }
585 faces_offset.add(faces_connectivity.size());
586 polyhedron_to_faces.add(nb_face);
587 ++nb_face;
588 }
589 }
590 polyhedron_offset[index + 1] = polyhedron_to_faces.size();
591 }
592 }
593
594 // Determines whether the datasets related to the polyhedron cells need to
595 // be written. This is the case either when the mesh contains at least one
596 // polyhedron cell or when these datasets have already been created for a
597 // previous time step (their content must then be updated at each time step
598 // to keep the temporal offsets of the file consistent).
599 {
600 Int32 local_has_polyhedron = (nb_face > 0) ? 1 : 0;
601 Int32 global_has_polyhedron = pm->reduce(MessagePassing::ReduceMax, local_has_polyhedron);
602 Int32 global_has_polyhedron_datasets = 0;
603 if (!m_is_first_call) {
604 Int32 local_has_polyhedron_datasets = 0;
605 if (m_is_writer) {
606 HDataset polyhedron_dataset;
607 polyhedron_dataset.openIfExists(m_top_group, "FaceConnectivity");
608 local_has_polyhedron_datasets = (polyhedron_dataset.id() > 0) ? 1 : 0;
609 }
610 global_has_polyhedron_datasets = pm->reduce(MessagePassing::ReduceMax, local_has_polyhedron_datasets);
611 }
612 if (global_has_polyhedron != 0 && global_has_polyhedron_datasets == 0 && !m_is_first_call)
613 ARCANE_FATAL("The mesh contains polyhedron cells but the associated datasets "
614 "do not exist in the file '{0}'. Polyhedron cells must be present "
615 "from the first output",
616 m_full_filename);
617 m_write_polyhedron_datasets = (global_has_polyhedron != 0) || (global_has_polyhedron_datasets != 0);
618 }
619
620 _initializeOffsets();
621
622 // ggi is a GatherGroupInfo for arrays that are not of size nb_cell or nb_node.
623 Ref<GatherGroupInfo> ggi_ref = createRef<GatherGroupInfo>(m_mesh->parallelMng(), m_is_collective_io);
624 GatherGroupInfo* ggi = ggi_ref.get();
625
626 // TODO: create an offset for this object (or look into how to calculate it automatically
627 _writeDataSet1DCollective<Int64>(DataInfo(m_top_group, "Offsets", m_offset_for_cell_offset_info),
628 ggi, cells_offset);
629
630 if (m_write_polyhedron_datasets) {
631 // Saves the offset of the faces of each cell in 'PolyhedronToFaces'.
632 // This dataset has the same structure as 'Offsets' ((nb_cell + 1) values
633 // per part) and is therefore written with the same gather information.
634 _writeDataSet1DCollective<Int64>(DataInfo(m_top_group, "PolyhedronOffsets", m_offset_for_polyhedron_offset_info),
635 ggi, polyhedron_offset);
636 }
637 ggi->setNeedRecompute();
638
639 _writeDataSet1DCollective<Int64>(DataInfo(m_top_group, "Connectivity", m_connectivity_offset_info),
640 ggi, cells_connectivity);
641 ggi->setNeedRecompute();
642
643 if (m_write_polyhedron_datasets) {
644 // Saves the nodes of the faces of the polyhedron cells.
645 _writeDataSet1DCollective<Int64>(DataInfo(m_top_group, "FaceConnectivity", m_face_connectivity_offset_info),
646 ggi, faces_connectivity);
647 ggi->setNeedRecompute();
648
649 // Saves the faces of each cell. Cells that are not polyhedron cells
650 // have an empty list of faces.
651 _writeDataSet1DCollective<Int64>(DataInfo(m_top_group, "PolyhedronToFaces", m_polyhedron_to_face_offset_info),
652 ggi, polyhedron_to_faces);
653 ggi->setNeedRecompute();
654
655 // Saves the offsets of the faces in 'FaceConnectivity'. The array size
656 // is equal to the number of faces plus 1.
657 _writeDataSet1DCollective<Int64>(DataInfo(m_top_group, "FaceOffsets", m_offset_for_face_offset_info),
658 ggi, faces_offset);
659 ggi->setNeedRecompute();
660 }
661
662 _writeDataSet1DCollective<unsigned char>(DataInfo(m_top_group, "Types", m_cell_offset_info),
663 &m_all_cells_gather_group_info, cells_type);
664
665 {
666 Int64 nb_cell_int64 = nb_cell;
667 _writeDataSet1DCollective<Int64>(DataInfo(m_top_group, "NumberOfCells", m_part_offset_info),
668 ggi, asConstSpan(&nb_cell_int64));
669
670 Int64 nb_node_int64 = nb_node;
671 _writeDataSet1DCollective<Int64>(DataInfo(m_top_group, "NumberOfPoints", m_part_offset_info),
672 ggi, asConstSpan(&nb_node_int64));
673
674 Int64 number_of_connectivity_ids = cells_connectivity.size();
675 _writeDataSet1DCollective<Int64>(DataInfo(m_top_group, "NumberOfConnectivityIds", m_part_offset_info),
676 ggi, asConstSpan(&number_of_connectivity_ids));
677
678 if (m_write_polyhedron_datasets) {
679 Int64 nb_face_int64 = nb_face;
680 _writeDataSet1DCollective<Int64>(DataInfo(m_top_group, "NumberOfFaces", m_part_offset_info),
681 ggi, asConstSpan(&nb_face_int64));
682
683 Int64 number_of_face_connectivity_ids = faces_connectivity.size();
684 _writeDataSet1DCollective<Int64>(DataInfo(m_top_group, "NumberOfFaceConnectivityIds", m_part_offset_info),
685 ggi, asConstSpan(&number_of_face_connectivity_ids));
686
687 Int64 number_of_polyhedron_to_face_ids = polyhedron_to_faces.size();
688 _writeDataSet1DCollective<Int64>(DataInfo(m_top_group, "NumberOfPolyhedronToFaceIds", m_part_offset_info),
689 ggi, asConstSpan(&number_of_polyhedron_to_face_ids));
690 }
691 }
692 ggi->setNeedRecompute();
693
694 // Saves the unique IDs, types, and coordinates of the nodes.
695 {
696 UniqueArray<Int64> nodes_uid(nb_node);
697 UniqueArray<unsigned char> nodes_ghost_type(nb_node);
698 VariableNodeReal3& nodes_coordinates(m_mesh->nodesCoordinates());
699 UniqueArray2<Real> points;
700 points.resize(nb_node, 3);
701 ENUMERATE_ (Node, inode, all_nodes) {
702 Int32 index = inode.index();
703 Node node = *inode;
704
705 nodes_uid[index] = node.uniqueId();
706
707 Byte ghost_type = 0;
708 bool is_ghost = !node.isOwn();
709 if (is_ghost)
710 ghost_type = VtkUtils::PointGhostTypes::DUPLICATEPOINT;
711 nodes_ghost_type[index] = ghost_type;
712
713 Real3 pos = nodes_coordinates[inode];
714 points[index][0] = pos.x;
715 points[index][1] = pos.y;
716 points[index][2] = pos.z;
717 }
718
719 // Saves the unique ID of each node in the dataset "GlobalNodeId".
720 _writeDataSet1DCollective<Int64>(DataInfo(DatasetGroupAndName(m_node_data_group, "GlobalIds"), m_cell_offset_info),
721 &m_all_nodes_gather_group_info, nodes_uid);
722
723 // Saves the information about the node type (real or ghost).
724 _writeDataSet1DCollective<unsigned char>(DataInfo(DatasetGroupAndName(m_node_data_group, "vtkGhostType"), m_cell_offset_info),
725 &m_all_nodes_gather_group_info, nodes_ghost_type);
726
727 // Saves the coordinates of the nodes.
728 _writeDataSet2DCollective<Real>(DataInfo(DatasetGroupAndName(m_top_group, "Points"), m_point_offset_info),
729 ggi, points);
730 ggi->setNeedRecompute();
731 }
732
733 // Saves the information about the cell type (real or ghost)
734 _writeDataSet1DCollective<unsigned char>(DataInfo(DatasetGroupAndName(m_cell_data_group, "vtkGhostType"), m_cell_offset_info),
735 &m_all_cells_gather_group_info, cells_ghost_type);
736
737 // Saves the unique ID of each cell in the dataset "GlobalCellId".
738 // Using the dataset "vtkOriginalCellIds" does not work in Paraview.
739 _writeDataSet1DCollective<Int64>(DataInfo(DatasetGroupAndName(m_cell_data_group, "GlobalIds"), m_cell_offset_info),
740 &m_all_cells_gather_group_info, cells_uid);
741
742 if (m_is_writer) {
743 // List of times.
744 Real current_time = m_times[time_index - 1];
745 // TODO: Replace ggi with nullptr in non-collective mode?
746 _writeDataSet1D<Real>(DataInfo(DatasetGroupAndName(m_steps_group, "Values"), m_time_offset_info),
747 ggi, asConstSpan(&current_time));
748
749 // Part offset.
750 Int64 comm_size = pm->commSize();
751 Int64 part_offset = (time_index - 1) * comm_size;
752 _writeDataSet1D<Int64>(DataInfo(DatasetGroupAndName(m_steps_group, "PartOffsets"), m_time_offset_info),
753 ggi, asConstSpan(&part_offset));
754
755 if (m_write_polyhedron_datasets) {
756 // Offset of the 'FaceOffsets' dataset for the current time step. This
757 // value is the number of faces saved before the current time step. It
758 // is deduced from the write offset of the 'FaceOffsets' dataset (which
759 // also contains one offset value per part and per time step) by
760 // removing the number of these offset values.
761 auto face_offsets_write_offset = m_offset_info_list.find(m_offset_for_face_offset_info);
762 if (face_offsets_write_offset == m_offset_info_list.end())
763 ARCANE_FATAL("The dataset 'FaceOffsets' has not been written");
764 Int64 nb_offset_value = (time_index - 1) * comm_size;
765 Int64 face_offsets_offset = face_offsets_write_offset->second - nb_offset_value;
766 _writeDataSet1D<Int64>({ { m_steps_group, "FaceOffsetsOffsets" }, m_time_offset_info }, ggi,
767 asConstSpan(&face_offsets_offset));
768 }
769
770 // Number of times
771 _addInt64Attribute(m_steps_group, "NSteps", time_index);
772 }
773 //ggi->needRecompute();
774
775 _writeConstituentsGroups();
776}
777
778/*---------------------------------------------------------------------------*/
779/*---------------------------------------------------------------------------*/
780
781void VtkHdfV2DataWriter::
782_writeConstituentsGroups()
783{
784 if (!m_material_mng)
785 return;
786
787 // Fills the information for groups related to constituents
788 // NOTE: For now, we only process the media.
789 for (IMeshEnvironment* env : m_material_mng->environments()) {
790 CellGroup cells = env->cells();
791
792 Ref<ItemGroupCollectiveInfo> group_info_ref = createRef<ItemGroupCollectiveInfo>(cells);
793 m_materials_groups.add(group_info_ref);
794
795 Ref<GatherGroupInfo> gather_info_ref = createRef<GatherGroupInfo>(m_material_mng->mesh()->parallelMng(), m_is_collective_io);
796 m_gather_info_materials_groups.add(gather_info_ref);
797
798 ItemGroupCollectiveInfo& group_info = *group_info_ref.get();
799 GatherGroupInfo& gather_info = *gather_info_ref.get();
800 _initializeItemGroupCollectiveInfos(group_info, gather_info);
801
802 ConstArrayView<Int32> groups_ids = cells.view().localIds();
803 DatasetGroupAndName dataset_group_name(m_top_group, String("Constituent_") + cells.name());
804 if (m_is_first_call)
805 info() << "Writing infos for group '" << cells.name() << "'";
806 _writeDataSet1DCollective<Int32>({ dataset_group_name, m_cell_offset_info, group_info_ref.get() }, gather_info_ref.get(), groups_ids);
807 }
808}
809
810/*---------------------------------------------------------------------------*/
811/*---------------------------------------------------------------------------*/
812/*!
813 * \brief Computes the offset of our part and the total number of elements.
814 */
815VtkHdfV2DataWriter::WritePartInfo VtkHdfV2DataWriter::
816_computeWritePartInfo(Int64 local_size)
817{
818 // TODO: look into using a scan.
819 IParallelMng* pm = m_mesh->parallelMng();
820 Int32 nb_rank = pm->commSize();
821 Int32 my_rank = pm->commRank();
822
823 UniqueArray<Int64> ranks_size(nb_rank);
824 ArrayView<Int64> all_sizes(ranks_size);
825 Int64 dim1_size = local_size;
826 pm->allGather(ConstArrayView<Int64>(1, &dim1_size), all_sizes);
827
828 Int64 total_size = 0;
829 for (Integer i = 0; i < nb_rank; ++i)
830 total_size += all_sizes[i];
831
832 Int64 my_index = 0;
833 for (Integer i = 0; i < my_rank; ++i)
834 my_index += all_sizes[i];
835
836 WritePartInfo part_info;
837 part_info.setTotalSize(total_size);
838 part_info.setSize(local_size);
839 part_info.setOffset(my_index);
840 return part_info;
841}
842
843/*---------------------------------------------------------------------------*/
844/*---------------------------------------------------------------------------*/
845
846void VtkHdfV2DataWriter::
847_initializeItemGroupCollectiveInfos(ItemGroupCollectiveInfo& group_info, GatherGroupInfo& gather_info)
848{
849 Int32 dim1_size = group_info.m_item_group.size();
850
851 gather_info.setCollectiveIO(m_is_collective_io);
852 gather_info.computeSize(dim1_size);
853
854 Int32 computed_nb_elem = gather_info.nbElemOutput();
855
856 group_info.setWritePartInfo(_computeWritePartInfo(computed_nb_elem));
857}
858
859namespace
860{
861 std::pair<Int64, Int64> _getInterval(Int64 index, Int64 nb_interval, Int64 total_size)
862 {
863 Int64 n = total_size;
864 Int64 isize = n / nb_interval;
865 Int64 ibegin = index * isize;
866 // For the last interval, take the remaining elements
867 if ((index + 1) == nb_interval)
868 isize = n - ibegin;
869 return { ibegin, isize };
870 }
871} // namespace
872
873/*---------------------------------------------------------------------------*/
874/*---------------------------------------------------------------------------*/
875/*!
876 * \brief Writes a 1D or 2D data.
877 *
878 * For each time added, the data is written at the end of previous values
879 * unless rolling back, in which case the offset is in data_info.
880 */
881void VtkHdfV2DataWriter::
882_writeDataSetGeneric(const DataInfo& data_info, GatherGroupInfo* gather_info, Int32 nb_dim,
883 Int64 dim1_size, Int64 dim2_size,
884 ConstMemoryView values_data,
885 const hid_t hdf_type, bool is_collective)
886{
887 ARCANE_CHECK_POINTER(gather_info);
888 if (nb_dim == 1)
889 dim2_size = 1;
890
891 HGroup& group = data_info.dataset.group;
892 const String& name = data_info.dataset.name;
893
894 // If positive or zero, indicates the write offset.
895 // Otherwise, we write to the end of the current dataset.
896 Int64 wanted_offset = data_info.datasetInfo().offset();
897
898 static constexpr int MAX_DIM = 2;
899 HDataset dataset;
900
901 // In case of collective operation, local_dims and global_dims are
902 // different on the first dimension. The second dimension is always
903 // identical for local_dims and global_dims and should not be modified during
904 // the entire calculation.
905
906 // Dimensions of the dataset that the current rank will write.
907 FixedArray<hsize_t, MAX_DIM> local_dims;
908 local_dims[0] = dim1_size;
909 local_dims[1] = dim2_size;
910
911 // Cumulative dimensions of all ranks for writing.
912 FixedArray<hsize_t, MAX_DIM> global_dims;
913
914 // Maximum dimensions of the DataSet
915 // For the second dimension, we assume it is constant over time.
916 FixedArray<hsize_t, MAX_DIM> max_dims;
917 max_dims[0] = H5S_UNLIMITED;
918 max_dims[1] = dim2_size;
919
920 herr_t herror = 0;
921 Int64 write_offset = 0;
922
923 Int64 my_index = 0;
924 Int64 global_dim1_size = dim1_size;
925 Int32 nb_participating_rank = 1;
926
927 if (is_collective) {
928 nb_participating_rank = gather_info->nbWriterGlobal();
929 WritePartInfo part_info;
930 if (data_info.m_group_info) {
931 // If the data is associated with a group, then the information
932 // about the offset has already been calculated
933 part_info = data_info.m_group_info->writePartInfo();
934 }
935 else {
936 part_info = _computeWritePartInfo(dim1_size);
937 }
938 global_dim1_size = part_info.totalSize();
939 my_index = part_info.offset();
940 }
941
942 // The only collective operation was _computeWritePartInfo().
943 if (!m_is_writer) {
944 return;
945 }
946
947 HProperty write_plist_id;
948 if constexpr (HInit::hasParallelHdf5()) {
949 if (is_collective)
950 write_plist_id.createDatasetTransfertCollectiveMPIIO();
951 else
952 write_plist_id.createDatasetTransfertIndependentMPIIO();
953 }
954
955 HSpace file_space;
956 FixedArray<hsize_t, MAX_DIM> hyperslab_offsets;
957
958 if (m_is_first_call) {
959 // TODO: look into how to better calculate the chunk
960 FixedArray<hsize_t, MAX_DIM> chunk_dims;
961 global_dims[0] = global_dim1_size;
962 global_dims[1] = dim2_size;
963 // It is important that everyone has the same chunk size.
964 Int64 chunk_size = global_dim1_size / nb_participating_rank;
965 if (chunk_size < 1024)
966 chunk_size = 1024;
967 const Int64 max_chunk_size = 1024 * 1024 * 10;
968 chunk_size = math::min(chunk_size, max_chunk_size);
969 chunk_dims[0] = chunk_size;
970 chunk_dims[1] = dim2_size;
971 info() << "CHUNK nb_dim=" << nb_dim
972 << " global_dim1_size=" << global_dim1_size
973 << " chunk0=" << chunk_dims[0]
974 << " chunk1=" << chunk_dims[1]
975 << " name=" << name;
976 file_space.createSimple(nb_dim, global_dims.data(), max_dims.data());
977 HProperty plist_id;
978 plist_id.create(H5P_DATASET_CREATE);
979 H5Pset_chunk(plist_id.id(), nb_dim, chunk_dims.data());
980 dataset.create(group, name.localstr(), hdf_type, file_space, HProperty{}, plist_id, HProperty{});
981
982 if (is_collective) {
983 hyperslab_offsets[0] = my_index;
984 hyperslab_offsets[1] = 0;
985 }
986 }
987 else {
988 // Expands the first dimension of the dataset.
989 // We are going to add 'global_dim1_size' to this dimension.
990 dataset.open(group, name.localstr());
991 file_space = dataset.getSpace();
992 int nb_dimension = file_space.nbDimension();
993 if (nb_dimension != nb_dim)
994 ARCANE_THROW(IOException, "Bad dimension '{0}' for dataset '{1}' (should be 1)",
995 nb_dimension, name);
996 // TODO: Check that the second dimension is the same as the one saved.
997 FixedArray<hsize_t, MAX_DIM> original_dims;
998 file_space.getDimensions(original_dims.data(), nullptr);
999 hsize_t offset0 = original_dims[0];
1000 // If we have a positive offset from DatasetInfo, we take it.
1001 // This means we have performed a rollback.
1002 if (wanted_offset >= 0) {
1003 offset0 = wanted_offset;
1004 info() << "Forcing offset to " << wanted_offset;
1005 }
1006 global_dims[0] = offset0 + global_dim1_size;
1007 global_dims[1] = dim2_size;
1008 write_offset = offset0;
1009 // Expands the dataset.
1010 // WARNING this invalidates file_space. It must therefore be re-read immediately after.
1011 if ((herror = dataset.setExtent(global_dims.data())) < 0)
1012 ARCANE_THROW(IOException, "Can not extent dataset '{0}' (err={1})", name, herror);
1013 file_space = dataset.getSpace();
1014
1015 hyperslab_offsets[0] = offset0 + my_index;
1016 hyperslab_offsets[1] = 0;
1017 info(4) << "APPEND nb_dim=" << nb_dim
1018 << " dim0=" << global_dims[0]
1019 << " count0=" << local_dims[0]
1020 << " offsets0=" << hyperslab_offsets[0] << " name=" << name;
1021 }
1022
1023 Int64 nb_write_byte = global_dim1_size * dim2_size * values_data.datatypeSize();
1024
1025 // Performs the writing in multiple parts if requested.
1026 // This is only possible for collective writing.
1027 Int64 nb_interval = 1;
1028 if (is_collective && m_max_write_size > 0) {
1029 nb_interval = 1 + nb_write_byte / (m_max_write_size * 1024);
1030 }
1031 info(4) << "WRITE global_size=" << nb_write_byte << " max_size=" << m_max_write_size << " nb_interval=" << nb_interval;
1032
1033 for (Int64 i = 0; i < nb_interval; ++i) {
1034 auto [index, nb_element] = _getInterval(i, nb_interval, dim1_size);
1035 // Selects the part of the data to write
1036 FixedArray<hsize_t, 2> dims;
1037 dims[0] = nb_element;
1038 dims[1] = dim2_size;
1039 FixedArray<hsize_t, 2> offsets;
1040 offsets[0] = hyperslab_offsets[0] + index;
1041 offsets[1] = 0;
1042 if ((herror = H5Sselect_hyperslab(file_space.id(), H5S_SELECT_SET, offsets.data(), nullptr, dims.data(), nullptr)) < 0)
1043 ARCANE_THROW(IOException, "Can not select hyperslab '{0}' (err={1})", name, herror);
1044
1045 HSpace memory_space;
1046 memory_space.createSimple(nb_dim, dims.data());
1047 Int64 data_offset = index * values_data.datatypeSize() * dim2_size;
1048 // Performs the writing
1049 if ((herror = dataset.write(hdf_type, values_data.data() + data_offset, memory_space, file_space, write_plist_id)) < 0)
1050 ARCANE_THROW(IOException, "Can not write dataset '{0}' (err={1})", name, herror);
1051
1052 if (dataset.isBad())
1053 ARCANE_THROW(IOException, "Can not write dataset '{0}'", name);
1054 }
1055
1056 if (!data_info.datasetInfo().isNull())
1057 m_offset_info_list.insert(std::make_pair(data_info.datasetInfo(), write_offset));
1058}
1059
1060/*---------------------------------------------------------------------------*/
1061/*---------------------------------------------------------------------------*/
1062
1063template <typename DataType> void VtkHdfV2DataWriter::
1064_writeDataSetGeneric(const DataInfo& data_info, GatherGroupInfo* gather_info, Int32 nb_dim,
1065 Int64 dim1_size, Int64 dim2_size, const DataType* values_data,
1066 bool is_collective)
1067{
1068 const hid_t hdf_type = m_standard_types.nativeType(DataType{});
1069 ConstMemoryView mem_view = makeConstMemoryView(values_data, sizeof(DataType), dim1_size * dim2_size);
1070 _writeDataSetGeneric(data_info, gather_info, nb_dim, dim1_size, dim2_size, mem_view, hdf_type, is_collective);
1071}
1072
1073/*---------------------------------------------------------------------------*/
1074/*---------------------------------------------------------------------------*/
1075
1076template <typename DataType> void VtkHdfV2DataWriter::
1077_writeDataSet1D(const DataInfo& data_info, GatherGroupInfo* gather_info, Span<const DataType> values)
1078{
1079 _writeDataSetGeneric(data_info, gather_info, 1, values.size(), 1, values.data(), false);
1080}
1081
1082/*---------------------------------------------------------------------------*/
1083/*---------------------------------------------------------------------------*/
1084
1085template <typename DataType> void VtkHdfV2DataWriter::
1086_writeDataSet1DUsingCollectiveIO(const DataInfo& data_info, GatherGroupInfo* gather_info, Span<const DataType> values)
1087{
1088 _writeDataSetGeneric(data_info, gather_info, 1, values.size(), 1, values.data(), true);
1089}
1090
1091/*---------------------------------------------------------------------------*/
1092/*---------------------------------------------------------------------------*/
1093
1094template <typename DataType> void VtkHdfV2DataWriter::
1095_writeDataSet1DCollective(const DataInfo& data_info, GatherGroupInfo* gather_info, Span<const DataType> values)
1096{
1097 ARCANE_CHECK_POINTER(gather_info);
1098
1099 GatherGroup gg;
1100
1101 gather_info->computeSizeT(values);
1102 gg.setGatherGroupInfo(gather_info);
1103
1104 if (gg.isNeedGather()) {
1105 UniqueArray<DataType> all_values;
1106 gg.gatherToMasterIOT(values, all_values);
1107
1108 if (m_is_collective_io)
1109 _writeDataSet1DUsingCollectiveIO(data_info, gather_info, all_values.constSpan());
1110 else
1111 _writeDataSet1D(data_info, gather_info, all_values.constSpan());
1112 }
1113 else {
1114 if (m_is_collective_io)
1115 _writeDataSet1DUsingCollectiveIO(data_info, gather_info, values);
1116 else
1117 _writeDataSet1D(data_info, gather_info, values);
1118 }
1119}
1120
1121/*---------------------------------------------------------------------------*/
1122/*---------------------------------------------------------------------------*/
1123
1124template <typename DataType> void VtkHdfV2DataWriter::
1125_writeDataSet2D(const DataInfo& data_info, GatherGroupInfo* gather_info, Span2<const DataType> values)
1126{
1127 _writeDataSetGeneric(data_info, gather_info, 2, values.dim1Size(), values.dim2Size(), values.data(), false);
1128}
1129
1130/*---------------------------------------------------------------------------*/
1131/*---------------------------------------------------------------------------*/
1132
1133template <typename DataType> void VtkHdfV2DataWriter::
1134_writeDataSet2DUsingCollectiveIO(const DataInfo& data_info, GatherGroupInfo* gather_info, Span2<const DataType> values)
1135{
1136 _writeDataSetGeneric(data_info, gather_info, 2, values.dim1Size(), values.dim2Size(), values.data(), true);
1137}
1138
1139/*---------------------------------------------------------------------------*/
1140/*---------------------------------------------------------------------------*/
1141
1142template <typename DataType> void VtkHdfV2DataWriter::
1143_writeDataSet2DCollective(const DataInfo& data_info, GatherGroupInfo* gather_info, Span2<const DataType> values)
1144{
1145 ARCANE_CHECK_POINTER(gather_info);
1146
1147 GatherGroup gg;
1148
1149 gather_info->computeSizeT(values);
1150 gg.setGatherGroupInfo(gather_info);
1151
1152 if (gg.isNeedGather()) {
1153 UniqueArray2<DataType> all_values;
1154 gg.gatherToMasterIOT(values, all_values);
1155
1156 if (m_is_collective_io)
1157 _writeDataSet2DUsingCollectiveIO(data_info, gather_info, all_values.constSpan());
1158 else
1159 _writeDataSet2D(data_info, gather_info, all_values.constSpan());
1160 }
1161 else {
1162 if (m_is_collective_io)
1163 _writeDataSet2DUsingCollectiveIO(data_info, gather_info, values);
1164 else
1165 _writeDataSet2D(data_info, gather_info, values);
1166 }
1167}
1168
1169/*---------------------------------------------------------------------------*/
1170/*---------------------------------------------------------------------------*/
1171
1172void VtkHdfV2DataWriter::
1173_addInt64ArrayAttribute(Hid& hid, const char* name, Span<const Int64> values)
1174{
1175 hsize_t len = values.size();
1176 hid_t aid = H5Screate_simple(1, &len, nullptr);
1177 hid_t attr = H5Acreate2(hid.id(), name, H5T_NATIVE_INT64, aid, H5P_DEFAULT, H5P_DEFAULT);
1178 if (attr < 0)
1179 ARCANE_FATAL("Can not create attribute '{0}'", name);
1180 int ret = H5Awrite(attr, H5T_NATIVE_INT64, values.data());
1181 if (ret < 0)
1182 ARCANE_FATAL("Can not write attribute '{0}'", name);
1183 H5Aclose(attr);
1184 H5Sclose(aid);
1185}
1186
1187/*---------------------------------------------------------------------------*/
1188/*---------------------------------------------------------------------------*/
1189
1190void VtkHdfV2DataWriter::
1191_addInt64Attribute(Hid& hid, const char* name, Int64 value)
1192{
1193 HSpace aid(H5Screate(H5S_SCALAR));
1194 HAttribute attr;
1195 if (m_is_first_call)
1196 attr.create(hid, name, H5T_NATIVE_INT64, aid);
1197 else
1198 attr.open(hid, name);
1199 if (attr.isBad())
1200 ARCANE_FATAL("Can not create attribute '{0}'", name);
1201 herr_t ret = attr.write(H5T_NATIVE_INT64, &value);
1202 if (ret < 0)
1203 ARCANE_FATAL("Can not write attribute '{0}'", name);
1204}
1205
1206/*---------------------------------------------------------------------------*/
1207/*---------------------------------------------------------------------------*/
1208
1209Int64 VtkHdfV2DataWriter::
1210_readInt64Attribute(Hid& hid, const char* name)
1211{
1212 HAttribute attr;
1213 attr.open(hid, name);
1214 if (attr.isBad())
1215 ARCANE_FATAL("Can not open attribute '{0}'", name);
1216 Int64 value;
1217 herr_t ret = attr.read(H5T_NATIVE_INT64, &value);
1218 if (ret < 0)
1219 ARCANE_FATAL("Can not read attribute '{0}'", name);
1220 return value;
1221}
1222
1223/*---------------------------------------------------------------------------*/
1224/*---------------------------------------------------------------------------*/
1225
1226void VtkHdfV2DataWriter::
1227_addStringAttribute(Hid& hid, const char* name, const String& value)
1228{
1229 hid_t aid = H5Screate(H5S_SCALAR);
1230 hid_t attr_type = H5Tcopy(H5T_C_S1);
1231 H5Tset_size(attr_type, value.length());
1232 hid_t attr = H5Acreate2(hid.id(), name, attr_type, aid, H5P_DEFAULT, H5P_DEFAULT);
1233 if (attr < 0)
1234 ARCANE_FATAL("Can not create attribute {0}", name);
1235 int ret = H5Awrite(attr, attr_type, value.localstr());
1236 ret = H5Tclose(attr_type);
1237 if (ret < 0)
1238 ARCANE_FATAL("Can not write attribute '{0}'", name);
1239 H5Aclose(attr);
1240 H5Sclose(aid);
1241}
1242
1243/*---------------------------------------------------------------------------*/
1244/*---------------------------------------------------------------------------*/
1245
1246void VtkHdfV2DataWriter::
1247endWrite()
1248{
1249 // Save the recorded offsets
1250
1251 if (m_is_writer) {
1252 for (const auto& i : m_offset_info_list) {
1253 Int64 offset = i.second;
1254 const DatasetInfo& offset_info = i.first;
1255 HGroup* hdf_group = offset_info.group();
1256 //info() << "OFFSET_INFO name=" << offset_info.name() << " offset=" << offset;
1257 if (hdf_group) {
1258 Ref<GatherGroupInfo> ggi = createRef<GatherGroupInfo>(m_mesh->parallelMng(), m_is_collective_io);
1259 _writeDataSet1D<Int64>({ { *hdf_group, offset_info.name() }, m_time_offset_info }, ggi.get(), asConstSpan(&offset));
1260 }
1261 }
1262 }
1263 _closeGroups();
1264 m_file_id.close();
1265}
1266
1267/*---------------------------------------------------------------------------*/
1268/*---------------------------------------------------------------------------*/
1269
1270void VtkHdfV2DataWriter::
1271_openOrCreateGroups()
1272{
1273 // Any group opened here must be closed in closeGroups().
1274 m_top_group.openOrCreate(m_file_id, "VTKHDF");
1275 m_cell_data_group.openOrCreate(m_top_group, "CellData");
1276 m_node_data_group.openOrCreate(m_top_group, "PointData");
1277 m_steps_group.openOrCreate(m_top_group, "Steps");
1278 m_point_data_offsets_group.openOrCreate(m_steps_group, "PointDataOffsets");
1279 m_cell_data_offsets_group.openOrCreate(m_steps_group, "CellDataOffsets");
1280 m_field_data_offsets_group.openOrCreate(m_steps_group, "FieldDataOffsets");
1281}
1282
1283/*---------------------------------------------------------------------------*/
1284/*---------------------------------------------------------------------------*/
1285
1286void VtkHdfV2DataWriter::
1287_closeGroups()
1288{
1289 m_cell_data_group.close();
1290 m_node_data_group.close();
1291 m_point_data_offsets_group.close();
1292 m_cell_data_offsets_group.close();
1293 m_field_data_offsets_group.close();
1294 m_steps_group.close();
1295 m_top_group.close();
1296}
1297
1298/*---------------------------------------------------------------------------*/
1299/*---------------------------------------------------------------------------*/
1300
1302setMetaData(const String& meta_data)
1303{
1304 ARCANE_UNUSED(meta_data);
1305}
1306
1307/*---------------------------------------------------------------------------*/
1308/*---------------------------------------------------------------------------*/
1309
1311write(IVariable* var, IData* data)
1312{
1313 info(4) << "Write VtkHdfV2 var=" << var->name();
1314
1315 eItemKind item_kind = var->itemKind();
1316
1317 if (var->dimension() != 1)
1318 ARCANE_FATAL("Only export of scalar item variable is implemented (name={0})", var->name());
1319 if (var->isPartial())
1320 ARCANE_FATAL("Export of partial variable is not implemented");
1321
1322 HGroup* group = nullptr;
1323 DatasetInfo offset_info;
1324 ItemGroupCollectiveInfo* group_info = nullptr;
1325 GatherGroupInfo* gather_info = nullptr;
1326 switch (item_kind) {
1327 case IK_Cell:
1328 group = &m_cell_data_group;
1329 offset_info = m_cell_offset_info;
1330 group_info = &m_all_cells_info;
1331 gather_info = &m_all_cells_gather_group_info;
1332 break;
1333 case IK_Node:
1334 group = &m_node_data_group;
1335 offset_info = m_point_offset_info;
1336 group_info = &m_all_nodes_info;
1337 gather_info = &m_all_nodes_gather_group_info;
1338 break;
1339 default:
1340 ARCANE_FATAL("Only export of 'Cell' or 'Node' variable is implemented (name={0})", var->name());
1341 }
1342
1343 ARCANE_CHECK_POINTER(group);
1344
1345 DataInfo data_info(DatasetGroupAndName{ *group, var->name() }, offset_info, group_info);
1346 eDataType data_type = var->dataType();
1347 switch (data_type) {
1348 case DT_Real:
1349 _writeBasicTypeDataset<Real>(data_info, gather_info, data);
1350 break;
1351 case DT_Int64:
1352 _writeBasicTypeDataset<Int64>(data_info, gather_info, data);
1353 break;
1354 case DT_Int32:
1355 _writeBasicTypeDataset<Int32>(data_info, gather_info, data);
1356 break;
1357 case DT_Real3:
1358 _writeReal3Dataset(data_info, gather_info, data);
1359 break;
1360 case DT_Real2:
1361 _writeReal2Dataset(data_info, gather_info, data);
1362 break;
1363 default:
1364 warning() << String::format("Export for datatype '{0}' is not supported (var_name={1})", data_type, var->name());
1365 }
1366}
1367
1368/*---------------------------------------------------------------------------*/
1369/*---------------------------------------------------------------------------*/
1370
1371template <typename DataType> void VtkHdfV2DataWriter::
1372_writeBasicTypeDataset(const DataInfo& data_info, GatherGroupInfo* gather_info, IData* data)
1373{
1374 auto* true_data = dynamic_cast<IArrayDataT<DataType>*>(data);
1375 ARCANE_CHECK_POINTER(true_data);
1376 _writeDataSet1DCollective(data_info, gather_info, Span<const DataType>(true_data->view()));
1377}
1378
1379/*---------------------------------------------------------------------------*/
1380/*---------------------------------------------------------------------------*/
1381
1382void VtkHdfV2DataWriter::
1383_writeReal3Dataset(const DataInfo& data_info, GatherGroupInfo* gather_info, IData* data)
1384{
1385 auto* true_data = dynamic_cast<IArrayDataT<Real3>*>(data);
1386 ARCANE_CHECK_POINTER(true_data);
1387 SmallSpan<const Real3> values(true_data->view());
1388 Int32 nb_value = values.size();
1389 // TODO: optimize this without passing through a temporary array
1390 UniqueArray2<Real> scalar_values;
1391 scalar_values.resize(nb_value, 3);
1392 for (Int32 i = 0; i < nb_value; ++i) {
1393 Real3 v = values[i];
1394 scalar_values[i][0] = v.x;
1395 scalar_values[i][1] = v.y;
1396 scalar_values[i][2] = v.z;
1397 }
1398 _writeDataSet2DCollective<Real>(data_info, gather_info, scalar_values);
1399}
1400
1401/*---------------------------------------------------------------------------*/
1402/*---------------------------------------------------------------------------*/
1403
1404void VtkHdfV2DataWriter::
1405_writeReal2Dataset(const DataInfo& data_info, GatherGroupInfo* gather_info, IData* data)
1406{
1407 // Convert to an array of 3 components where the last one will be 0.
1408 auto* true_data = dynamic_cast<IArrayDataT<Real2>*>(data);
1409 ARCANE_CHECK_POINTER(true_data);
1410 SmallSpan<const Real2> values(true_data->view());
1411 Int32 nb_value = values.size();
1412 UniqueArray2<Real> scalar_values;
1413 scalar_values.resize(nb_value, 3);
1414 for (Int32 i = 0; i < nb_value; ++i) {
1415 Real2 v = values[i];
1416 scalar_values[i][0] = v.x;
1417 scalar_values[i][1] = v.y;
1418 scalar_values[i][2] = 0.0;
1419 }
1420 _writeDataSet2DCollective<Real>(data_info, gather_info, scalar_values);
1421}
1422
1423/*---------------------------------------------------------------------------*/
1424/*---------------------------------------------------------------------------*/
1425
1426void VtkHdfV2DataWriter::
1427_readAndSetOffset(DatasetInfo& offset_info, Int32 wanted_step)
1428{
1429 HGroup* hgroup = offset_info.group();
1430 ARCANE_CHECK_POINTER(hgroup);
1431 StandardArrayT<Int64> a(hgroup->id(), offset_info.name());
1432 UniqueArray<Int64> values;
1433 a.directRead(m_standard_types, values);
1434 Int64 offset_value = values[wanted_step];
1435 offset_info.setOffset(offset_value);
1436 info() << "VALUES name=" << offset_info.name() << " values=" << values
1437 << " wanted_step=" << wanted_step << " v=" << offset_value;
1438}
1439
1440/*---------------------------------------------------------------------------*/
1441/*---------------------------------------------------------------------------*/
1442
1443void VtkHdfV2DataWriter::
1444_initializeOffsets()
1445{
1446 // There are 5 offset values used:
1447 //
1448 // - offset on the number of cells (CellOffsets). This offset has a number of elements
1449 // equal to the number of saved time steps and is increased at each number of cells output. This offset
1450 // is also used for cell variables
1451 // - offset on the number of nodes (PointOffsets). It is equivalent to 'CellOffsets' but
1452 // for nodes.
1453 // - offset for "NumberOfCells", "NumberOfPoints" and "NumberOfConnectivityIds". For each
1454 // of these fields there are NbPart values per time step, with 'NbPart' being the number of parts (and
1455 // thus the number of sub-domains if no grouping is done). There are thus a total of
1456 // NbPart * NbTimeStep in this offset field.
1457 // - offset for the "Connectivity" field, which is called "ConnectivityIdOffsets".
1458 // This offset has a number of elements equal to the number of saved time steps.
1459 // - offset for the "Offsets" field. "Offset" contains for each cell the offset in
1460 // "Connectivity" of the cell node connectivity. This offset is not saved,
1461 // but since this field has a number of values equal to the number of cells plus one it is possible
1462 // to deduce it from "CellOffsets" (it equals "CellOffsets" plus the current time index).
1463 //
1464 // When the mesh contains polyhedron cells, 4 additional offset values are used:
1465 //
1466 // - offset for the "FaceConnectivity" field, which is called "FaceConnectivityOffsets".
1467 // It is equivalent to "ConnectivityIdOffsets" but for the faces of the polyhedron cells.
1468 // - offset for the "PolyhedronToFaces" field, which is called "PolyhedronToFaceIdOffsets".
1469 // It is equivalent to "ConnectivityIdOffsets" but for the list of faces of each cell.
1470 // - offset for the "FaceOffsets" field, which is called "FaceOffsetsOffsets". It contains
1471 // the number of faces saved before the current time step. As for "Offsets", the write
1472 // offset of the "FaceOffsets" dataset can be deduced from it (it equals "FaceOffsetsOffsets"
1473 // plus the number of parts saved before the current time step).
1474 // - offset for the "PolyhedronOffsets" field. This offset is not saved but, as this field
1475 // has the same structure as "Offsets", its write offset is equal to the one of "Offsets".
1476
1477 m_cell_offset_info = DatasetInfo(m_steps_group, "CellOffsets");
1478 m_point_offset_info = DatasetInfo(m_steps_group, "PointOffsets");
1479 m_connectivity_offset_info = DatasetInfo(m_steps_group, "ConnectivityIdOffsets");
1480 m_face_connectivity_offset_info = DatasetInfo(m_steps_group, "FaceConnectivityOffsets");
1481 m_polyhedron_to_face_offset_info = DatasetInfo(m_steps_group, "PolyhedronToFaceIdOffsets");
1482 m_face_offsets_offset_info = DatasetInfo(m_steps_group, "FaceOffsetsOffsets");
1483 // These five offsets are not saved in the VTK format
1484 m_offset_for_cell_offset_info = DatasetInfo("_OffsetForCellOffsetInfo");
1485 m_offset_for_face_offset_info = DatasetInfo("_OffsetForFaceOffsetInfo");
1486 m_offset_for_polyhedron_offset_info = DatasetInfo("_OffsetForPolyhedronOffsetInfo");
1487 m_part_offset_info = DatasetInfo("_PartOffsetInfo");
1488 m_time_offset_info = DatasetInfo("_TimeOffsetInfo");
1489
1490 // Check if we haven't done a backward run.
1491 // This is the case if the number of saved time steps is greater than the number
1492 // of values in \a m_times.
1493 if (m_is_writer && !m_is_first_call) {
1494 IParallelMng* pm = m_mesh->parallelMng();
1495 const Int32 nb_rank = pm->commSize();
1496 Int64 nb_current_step = _readInt64Attribute(m_steps_group, "NSteps");
1497 Int32 time_index = m_times.size();
1498 info(4) << "NB_STEP=" << nb_current_step << " time_index=" << time_index
1499 << " current_time=" << m_times.back();
1500 const bool debug_times = false;
1501 if (debug_times) {
1502 StandardArrayT<Real> a1(m_steps_group.id(), "Values");
1503 UniqueArray<Real> times;
1504 a1.directRead(m_standard_types, times);
1505 info() << "TIMES=" << times;
1506 }
1507 if ((nb_current_step + 1) != time_index) {
1508 info() << "[VtkHdf] go_backward detected";
1509 Int32 wanted_step = time_index - 1;
1510 // This means a backward run has been performed.
1511 // In this case, the offsets must be reread
1512 _readAndSetOffset(m_cell_offset_info, wanted_step);
1513 _readAndSetOffset(m_point_offset_info, wanted_step);
1514 _readAndSetOffset(m_connectivity_offset_info, wanted_step);
1515 if (m_write_polyhedron_datasets) {
1516 _readAndSetOffset(m_face_connectivity_offset_info, wanted_step);
1517 _readAndSetOffset(m_polyhedron_to_face_offset_info, wanted_step);
1518 _readAndSetOffset(m_face_offsets_offset_info, wanted_step);
1519 // The 'FaceOffsets' dataset contains (nb_face + 1) values per part:
1520 // its write offset is deduced from 'FaceOffsetsOffsets'.
1521 m_offset_for_face_offset_info.setOffset(m_face_offsets_offset_info.offset() + wanted_step * nb_rank);
1522 // The 'PolyhedronOffsets' dataset has the same structure as 'Offsets':
1523 // its write offset is therefore the same.
1524 m_offset_for_polyhedron_offset_info.setOffset(m_offset_for_cell_offset_info.offset());
1525 }
1526 m_part_offset_info.setOffset(wanted_step * nb_rank);
1527 m_time_offset_info.setOffset(wanted_step);
1528 m_offset_for_cell_offset_info.setOffset(m_cell_offset_info.offset() + wanted_step * nb_rank);
1529 }
1530 }
1531}
1532
1533/*---------------------------------------------------------------------------*/
1534/*---------------------------------------------------------------------------*/
1535
1536/*---------------------------------------------------------------------------*/
1537/*---------------------------------------------------------------------------*/
1538/*!
1539 * \brief Post-processing in VtkHdf V2 format.
1540 */
1541class VtkHdfV2PostProcessor
1542: public ArcaneVtkHdfV2PostProcessorObject
1543{
1544 public:
1545
1546 explicit VtkHdfV2PostProcessor(const ServiceBuildInfo& sbi)
1547 : ArcaneVtkHdfV2PostProcessorObject(sbi)
1548 {
1549 }
1550
1551 IDataWriter* dataWriter() override { return m_writer.get(); }
1552 void notifyBeginWrite() override
1553 {
1554 bool use_collective_io = true;
1555 Int64 max_write_size = 0;
1556 if (options()) {
1557 use_collective_io = options()->useCollectiveWrite();
1558 max_write_size = options()->maxWriteSize();
1559 }
1560 auto w = std::make_unique<VtkHdfV2DataWriter>(mesh(), groups(), use_collective_io);
1561 w->setMaxWriteSize(max_write_size);
1562 w->setTimes(times());
1563 Directory dir(baseDirectoryName());
1564 w->setDirectoryName(dir.file("vtkhdfv2"));
1565 m_writer = std::move(w);
1566 }
1567 void notifyEndWrite() override
1568 {
1569 m_writer = nullptr;
1570 }
1571 void close() override {}
1572
1573 private:
1574
1575 std::unique_ptr<IDataWriter> m_writer;
1576};
1577
1578/*---------------------------------------------------------------------------*/
1579/*---------------------------------------------------------------------------*/
1580
1581ARCANE_REGISTER_SERVICE_VTKHDFV2POSTPROCESSOR(VtkHdfV2PostProcessor,
1583
1584/*---------------------------------------------------------------------------*/
1585/*---------------------------------------------------------------------------*/
1586
1587} // namespace Arcane
1588
1589/*---------------------------------------------------------------------------*/
1590/*---------------------------------------------------------------------------*/
#define ARCANE_CHECK_POINTER(ptr)
Macro returning the pointer ptr if it is not null or throwing an exception if it is null.
#define ARCANE_THROW(exception_class,...)
Macro for throwing an exception with formatting.
#define ARCANE_FATAL(...)
Macro throwing a FatalErrorException.
#define ENUMERATE_(type, name, group)
Generic enumerator for an entity group.
#define ENUMERATE_CELL(name, group)
Generic enumerator for a cell group.
String file(const String &file_name) const override
Returns the full path of the file file_name in the directory.
Definition Directory.cc:120
Encapsulates a hid_t for a group.
Definition Hdf5Utils.h:351
static void useMutex(bool is_active, IParallelMng *pm)
Function allowing activation or deactivation of locks on each HDF5 call.
Definition Hdf5Utils.cc:90
static constexpr bool hasParallelHdf5()
True HDF5 is compiled with MPI support.
Definition Hdf5Utils.h:119
Interface for writing variable data.
Definition IDataWriter.h:45
Interface of a data item.
Definition IData.h:34
Interface of a variable.
Definition IVariable.h:40
virtual eDataType dataType() const =0
Data type managed by the variable (Real, Integer, ...).
virtual eItemKind itemKind() const =0
Kind of mesh entities on which the variable is based.
virtual bool isPartial() const =0
Indicates if the variable is partial.
virtual Integer dimension() const =0
Dimension of the variable.
virtual String name() const =0
Variable name.
Mesh entity group.
Definition ItemGroup.h:51
Integer size() const
Number of elements in the group.
Definition ItemGroup.h:93
static IMeshMaterialMng * getReference(const MeshHandleOrMesh &mesh_handle, bool create=true)
Retrieves or creates the reference associated with mesh.
Structure containing the information to create a service.
View of an array of elements of type T.
Definition Span.h:633
TraceAccessor(ITraceMng *m)
Constructs an accessor via the trace manager m.
TraceMessage info() const
Flow for an information message.
TraceMessage warning() const
Flow for a warning message.
void write(IVariable *var, IData *data) override
Writes the data data of the variable var.
void setMetaData(const String &meta_data) override
Sets the metadata information.
Post-processing in VtkHdf V2 format.
__host__ __device__ Real2 min(Real2 a, Real2 b)
Returns the minimum of two Real2.
Definition MathUtils.h:346
ItemLocalIdListViewT< Node > NodeLocalIdView
View over the localId() of a list of nodes.
Definition ItemTypes.h:407
ItemGroupT< Cell > CellGroup
Group of cells.
Definition ItemTypes.h:184
ItemGroupT< Node > NodeGroup
Group of nodes.
Definition ItemTypes.h:168
MeshVariableScalarRefT< Node, Real3 > VariableNodeReal3
Coordinate type quantity at node.
Integer len(const char *s)
Returns the length of the string s.
Utility functions for Hdf5.
Definition Hdf5Utils.cc:35
Always enables tracing in Arcane parts concerning materials.
@ ReduceMax
Maximum of values.
-- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature --
Ref< TrueType > createRef(Args &&... args)
Creates an instance of type TrueType with arguments Args and returns a reference to it.
Collection< ItemGroup > ItemGroupCollection
Collection of mesh item groups.
std::int64_t Int64
Signed integer type of 64 bits.
Int32 Integer
Type representing an integer.
@ Polyhedral
Polyhedral mesh.
Definition MeshKind.h:38
bool operator<(const Item &item1, const Item &item2)
Compare two entities.
Definition Item.h:566
ConstMemoryView makeConstMemoryView(const void *ptr, Int32 datatype_size, Int64 nb_element)
Creates a read-only memory view.
Definition MemoryView.cc:36
eItemKind
Mesh entity type.
@ IK_Node
Node mesh entity.
@ IK_Cell
Cell mesh entity.
double Real
Type representing a real number.
unsigned char Byte
Type of a byte.
Definition BaseTypes.h:42
eDataType
Data type.
Definition DataTypes.h:41
@ DT_Int32
32-bit integer data type
Definition DataTypes.h:45
@ DT_Real3
Vector 3 data type.
Definition DataTypes.h:49
@ DT_Int64
64-bit integer data type
Definition DataTypes.h:46
@ DT_Real2
Vector 2 data type.
Definition DataTypes.h:48
@ DT_Real
Real data type.
Definition DataTypes.h:43
@ Cell
The mesh is AMR by cell.
Definition MeshKind.h:53
std::int32_t Int32
Signed integer type of 32 bits.
ConstArrayView< Real > RealConstArrayView
C equivalent of a 1D array of reals.
Definition UtilsTypes.h:482
Stores info about the data to be saved and the associated offset.
Class to store a pair (hdf_group, dataset_name).
Class to store offset information.
Int64 offset() const
Offset value. (-1) if writing to the end of the array.
Offset information for the part to write associated with a rank.