1 //                                               -*- C++ -*-
2 /**
3  *  @brief XMLH5StorageManager implements xml/h5 storage
4  *
5  *  Copyright 2005-2021 Airbus-EDF-IMACS-ONERA-Phimeca
6  *
7  *  This library is free software: you can redistribute it and/or modify
8  *  it under the terms of the GNU Lesser General Public License as published by
9  *  the Free Software Foundation, either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This library is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU Lesser General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Lesser General Public License
18  *  along with this library.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 #include "openturns/XMLH5StorageManager.hxx"
23 #include "openturns/PersistentObject.hxx"
24 #include "openturns/Os.hxx"
25 
26 #include <H5Cpp.h>
27 #include <libxml/tree.h>
28 
29 BEGIN_NAMESPACE_OPENTURNS
30 
31 
32 class XMLH5StorageManagerImplementation
33 {
34 public:
35 
XMLH5StorageManagerImplementation(const FileName & h5FileName)36   explicit XMLH5StorageManagerImplementation(const FileName & h5FileName)
37     : h5FileName_(h5FileName)
38   {}
39 
40   template <class CPP_Type>
41   void addIndexedValue(Pointer<StorageManager::InternalObject> & p_obj, UnsignedInteger index, CPP_Type value);
42 
43   template <class CPP_Type>
44   void readIndexedValue(Pointer<StorageManager::InternalObject> & p_obj, UnsignedInteger index, CPP_Type & value);
45 
46 private:
47   template <class CPP_Type>
48   void writeToH5(const String & dataSetName);
49 
50   template <class CPP_Type>
51   void readFromH5(const String & dataSetName);
52 
53   // Buffer size for writing hdf5 slabs
54   const UnsignedInteger BufferSize = 1048576;
55 
56   // 30kB (max recommended dataset header size)
57   // https://support.hdfgroup.org/HDF5/doc/UG/HDF5_Users_Guide.pdf
58   const UnsignedInteger MaxHeaderSize = 30720;
59 
60   template <class CPP_Type> inline std::vector<CPP_Type> & getBuffer();
61 
62   FileName h5FileName_;
63   std::vector<Scalar> valBuf_Scalar_;
64   std::vector<UnsignedInteger> valBuf_UnsignedInteger_;
65   OT::Bool isFirstDS_ = true;
66 };
67 
68 
getBuffer()69 template <> inline std::vector<Scalar> & XMLH5StorageManagerImplementation::getBuffer()
70 {
71   return valBuf_Scalar_;
72 }
getBuffer()73 template <> inline std::vector<UnsignedInteger> & XMLH5StorageManagerImplementation::getBuffer()
74 {
75   return valBuf_UnsignedInteger_;
76 }
77 
78 template <class CPP_Type> inline H5::DataType getDataType();
getDataType()79 template <> inline H5::DataType getDataType<Scalar>()
80 {
81   return H5::PredType::IEEE_F64LE;
82 }
getDataType()83 template <> inline H5::DataType getDataType<UnsignedInteger>()
84 {
85   return H5::PredType::NATIVE_ULONG;
86 }
87 
88 
89 template <class CPP_Type>
addIndexedValue(Pointer<StorageManager::InternalObject> & p_obj,UnsignedInteger index,CPP_Type value)90 void XMLH5StorageManagerImplementation::addIndexedValue(Pointer<StorageManager::InternalObject> & p_obj,
91     UnsignedInteger index,
92     CPP_Type value)
93 {
94   // Get XML node associated to the Collection
95   assert(p_obj);
96   XMLInternalObject & obj = dynamic_cast<XMLInternalObject&>(*p_obj);
97   XML::Node node = obj.node_;
98   assert(node);
99   const hsize_t dsetSize = std::stoi(XML::GetAttributeByName(node, "size"));
100 
101   // append value in buffer
102   getBuffer<CPP_Type>().push_back(value);
103 
104   // if last value or buffer full then write to dataset
105   if ((index == dsetSize - 1) || ((index % BufferSize == 0) && (index > 0)))
106   {
107     String dataSetName = XML::GetAttributeByName(node, "id");
108     writeToH5<CPP_Type>(dataSetName);
109   }
110 
111   // if last value then add XML node
112   if (index == dsetSize - 1)
113   {
114     String dataSetName = XML::GetAttributeByName(node, "id");
115     const size_t idx = h5FileName_.find_last_of(Os::GetDirectorySeparator());
116     const FileName h5FileNameRel = h5FileName_.substr(idx + 1);
117     XML::Node child = XML::NewNode(XML_STMGR::string_tag::Get(), h5FileNameRel + ":/" + dataSetName);
118     assert(child);
119     XML::AddChild( node, child );
120   }
121 }
122 
123 
124 template <class CPP_Type>
writeToH5(const String & dataSetName)125 void XMLH5StorageManagerImplementation::writeToH5(const String & dataSetName)
126 {
127   H5::Exception::dontPrint();
128   H5::H5File h5File;
129   if (isFirstDS_)
130   {
131     //Create new or overwrite existing
132     h5File = H5::H5File(h5FileName_.c_str(), H5F_ACC_TRUNC);
133     isFirstDS_ = false;
134   }
135   else
136   {
137     //R+W access
138     h5File = H5::H5File(h5FileName_.c_str(), H5F_ACC_RDWR);
139   }
140 
141   const hsize_t dims[1] = { getBuffer<CPP_Type>().size() };
142 
143   if (!H5Lexists(h5File.getId(), dataSetName.c_str(), H5P_DEFAULT))
144   {
145     //Dataset does not exist, need to create it and initialize it with first chunk
146     const hsize_t maxdims[1] = { H5S_UNLIMITED };
147     H5::DataSpace dsp;
148     H5::DSetCreatPropList prop;
149     if (getBuffer<CPP_Type>().size() < BufferSize)
150     {
151       dsp = H5::DataSpace(1, dims, dims);
152       // Set dataspace compact if dataset can fit into dataset header
153       // Set it contiguous otherwise
154       if (getBuffer<CPP_Type>().size()*sizeof(CPP_Type) < MaxHeaderSize)
155         prop.setLayout(H5D_COMPACT);
156       else
157         prop.setLayout(H5D_CONTIGUOUS);
158     }
159     //Set dataspace to unlimited if full buffer
160     else
161       dsp = H5::DataSpace(1, dims, maxdims);
162     //Propagate dataspace properties to dataset
163     prop.setChunk(1, dims);
164     //Create new dataset and write it
165     H5::DataSet dset(h5File.createDataSet(dataSetName, getDataType<CPP_Type>(), dsp, prop));
166     dset.write(getBuffer<CPP_Type>().data(), getDataType<CPP_Type>());
167     prop.close();
168   }
169   else
170   {
171     //Dataset exists, and will be appended with buffer values
172     H5::DataSet dset(h5File.openDataSet(dataSetName));
173     //Get actual dset size
174     const hsize_t offset[1] = { (hsize_t)dset.getSpace().getSimpleExtentNpoints() };
175     const hsize_t extent[1] = { dims[0] + offset[0] };
176     //Extend dset size by the buffer size
177     dset.extend(extent);
178     //Get updated dataspace
179     H5::DataSpace filespace(dset.getSpace());
180     filespace.selectHyperslab(H5S_SELECT_SET, dims, offset);
181     //Create space for new data
182     H5::DataSpace memspace(1, dims, NULL);
183     //Write new data
184     dset.write(getBuffer<CPP_Type>().data(), getDataType<CPP_Type>(), memspace, filespace);
185   }
186   getBuffer<CPP_Type>().clear();
187   h5File.close();
188 }
189 
190 
191 template <class CPP_Type>
readIndexedValue(Pointer<StorageManager::InternalObject> & p_obj,UnsignedInteger index,CPP_Type & value)192 void XMLH5StorageManagerImplementation::readIndexedValue(Pointer<StorageManager::InternalObject> & p_obj,
193     UnsignedInteger index,
194     CPP_Type & value)
195 {
196   assert(p_obj);
197   //Read values only once
198   XMLH5StorageManagerState & state = dynamic_cast<XMLH5StorageManagerState &>(*p_obj);
199 
200   if (index == 0)
201   {
202     XML::Node node = state.current_->parent;
203     String dataSetName = XML::GetAttributeByName(node, "id");
204     readFromH5<CPP_Type>(dataSetName);
205     state.reachedEnd_ = false;
206   }
207   if (index == getBuffer<CPP_Type>().size() - 1)
208   {
209     state.reachedEnd_ = true;
210   }
211   state.next();
212   //Get value from index
213   value = getBuffer<CPP_Type>()[index];
214 }
215 
216 
217 template <class CPP_Type>
readFromH5(const String & dataSetName)218 void XMLH5StorageManagerImplementation::readFromH5(const String & dataSetName)
219 {
220   H5::Exception::dontPrint();
221   H5::H5File file(h5FileName_.c_str(), H5F_ACC_RDONLY);
222   H5::DataSet dataset = file.openDataSet(dataSetName.c_str());
223   H5::DataSpace dataspace = dataset.getSpace();
224   const int size = dataspace.getSimpleExtentNpoints();
225 
226   getBuffer<CPP_Type>().resize(size);
227   dataset.read(getBuffer<CPP_Type>().data(), getDataType<CPP_Type>());
228 
229   dataspace.close();
230   dataset.close();
231   file.close();
232 }
233 
CLASSNAMEINIT(XMLH5StorageManager)234 CLASSNAMEINIT(XMLH5StorageManager)
235 
236 
237 /* Default constructor */
238 XMLH5StorageManager::XMLH5StorageManager(const FileName & filename,
239     const UnsignedInteger compressionLevel)
240   : XMLStorageManager(filename, compressionLevel)
241 {
242   p_state_ = new XMLH5StorageManagerState;
243   p_implementation_ = new XMLH5StorageManagerImplementation(filename.substr(0, filename.find_last_of('.')) + ".h5");
244 }
245 
246 /*
247  * Virtual constructor
248  */
clone() const249 XMLH5StorageManager * XMLH5StorageManager::clone() const
250 {
251   return new XMLH5StorageManager(*this);
252 }
253 
254 
checkStorageManager()255 void XMLH5StorageManager::checkStorageManager()
256 {
257   if (XML::GetAttributeByName( p_state_->root_, XML_STMGR::manager_attribute::Get()) !=
258       "XMLH5StorageManager")
259     throw StudyFileParsingException(HERE) << XML::GetAttributeByName( p_state_->root_, XML_STMGR::manager_attribute::Get())
260                                           << " is used in study file. XMLH5StorageManager is expected";
261 }
262 
263 
setStorageManager()264 void XMLH5StorageManager::setStorageManager()
265 {
266   XML::SetAttribute(p_state_->root_, XML_STMGR::manager_attribute::Get(), "XMLH5StorageManager");
267 }
268 
269 
addIndexedValue(Pointer<InternalObject> & p_obj,UnsignedInteger index,Scalar value)270 void XMLH5StorageManager::addIndexedValue(Pointer<InternalObject> & p_obj, UnsignedInteger index, Scalar value)
271 {
272   p_implementation_->addIndexedValue(p_obj, index, value);
273 }
274 
addIndexedValue(Pointer<InternalObject> & p_obj,UnsignedInteger index,UnsignedInteger value)275 void XMLH5StorageManager::addIndexedValue(Pointer<InternalObject> & p_obj, UnsignedInteger index, UnsignedInteger value)
276 {
277   p_implementation_->addIndexedValue(p_obj, index, value);
278 }
279 
readIndexedValue(Pointer<StorageManager::InternalObject> & p_obj,UnsignedInteger index,UnsignedInteger & value)280 void XMLH5StorageManager::readIndexedValue(Pointer<StorageManager::InternalObject> & p_obj, UnsignedInteger index, UnsignedInteger & value)
281 {
282   // we started to store integers into h5 in 1.17
283   if (getStudyVersion() >= 101700)
284     p_implementation_->readIndexedValue(p_obj, index, value);
285   else
286     XMLStorageManager::readIndexedValue(p_obj, index, value);
287 }
288 
readIndexedValue(Pointer<StorageManager::InternalObject> & p_obj,UnsignedInteger index,Scalar & value)289 void XMLH5StorageManager::readIndexedValue(Pointer<StorageManager::InternalObject> & p_obj, UnsignedInteger index, Scalar & value)
290 {
291   p_implementation_->readIndexedValue(p_obj, index, value);
292 }
293 
294 END_NAMESPACE_OPENTURNS
295