kspaceFirstOrder3D-OMP  1.2
The C++ implementation of the k-wave toolbox for the time-domain simulation of acoustic wave fields in 3D
CuboidOutputStream.cpp
Go to the documentation of this file.
1 /**
2  * @file CuboidOutputStream.cpp
3  *
4  * @author Jiri Jaros \n
5  * Faculty of Information Technology \n
6  * Brno University of Technology \n
7  * jarosjir@fit.vutbr.cz
8  *
9  * @brief The implementation file of classes responsible for storing output quantities based
10  * on the cuboid sensor mask into the output HDF5 file.
11  *
12  * @version kspaceFirstOrder3D 2.16
13  *
14  * @date 26 August 2017, 17:03 (created) \n
15  * 04 September 2017, 11:10 (revised)
16  *
17  * @copyright Copyright (C) 2017 Jiri Jaros and Bradley Treeby.
18  *
19  * This file is part of the C++ extension of the [k-Wave Toolbox](http://www.k-wave.org).
20  *
21  * This file is part of the k-Wave. k-Wave is free software: you can redistribute it and/or modify it under the terms
22  * of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the
23  * License, or (at your option) any later version.
24  *
25  * k-Wave is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
26  * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
27  * more details.
28  *
29  * You should have received a copy of the GNU Lesser General Public License along with k-Wave.
30  * If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/).
31  */
32 
33 #include <algorithm>
34 
36 #include <Parameters/Parameters.h>
37 
38 //--------------------------------------------------------------------------------------------------------------------//
39 //---------------------------------------------------- Constants -----------------------------------------------------//
40 //--------------------------------------------------------------------------------------------------------------------//
41 
42 
43 //--------------------------------------------------------------------------------------------------------------------//
44 //------------------------------------------------- Public methods ---------------------------------------------------//
45 //--------------------------------------------------------------------------------------------------------------------//
46 
47 /**
48  * Constructor - links the HDF5 dataset, SourceMatrix, and SensorMask together.
49  */
51  MatrixName& groupName,
52  const RealMatrix& sourceMatrix,
53  const IndexMatrix& sensorMask,
54  const ReduceOperator reduceOp,
55  float* bufferToReuse)
56  : BaseOutputStream(file, groupName, sourceMatrix, reduceOp, bufferToReuse),
57  mSensorMask(sensorMask),
58  mGroup(H5I_BADID),
59  mSampledTimeStep(0)
60 {
61 
62 }// end of CuboidOutputStream
63 //----------------------------------------------------------------------------------------------------------------------
64 
65 /**
66  * Destructor.
67  */
69 {
70  close();
71 
72  // free memory only if it was allocated
73  if (!mBufferReuse) freeMemory();
74 }// end ~CuboidOutputStream
75 //----------------------------------------------------------------------------------------------------------------------
76 
77 /**
78  * Create a HDF5 stream and allocate data for it. It also creates a HDF5 group with particular datasets
79  * (one per cuboid).
80  */
82 {
83  // Create the HDF5 group and open it
85 
86  // Create all datasets (sizes, chunks, and attributes)
87  size_t nCuboids = mSensorMask.getDimensionSizes().ny;
88  mCuboidsInfo.reserve(nCuboids);
89 
90  size_t actualPositionInBuffer = 0;
91 
92  for (size_t cuboidIdx = 0; cuboidIdx < nCuboids; cuboidIdx++)
93  {
94  CuboidInfo cuboidInfo;
95 
96  cuboidInfo.cuboidId = createCuboidDataset(cuboidIdx);
97  cuboidInfo.startingPossitionInBuffer = actualPositionInBuffer;
98  mCuboidsInfo.push_back(cuboidInfo);
99 
100  actualPositionInBuffer += (mSensorMask.getBottomRightCorner(cuboidIdx) -
101  mSensorMask.getTopLeftCorner(cuboidIdx)
102  ).nElements();
103  }
104 
105  //we're at the beginning
106  mSampledTimeStep = 0;
107 
108  // Create the memory buffer if necessary and set starting address
110 
111  // Allocate memory if needed
113 }// end of create
114 //----------------------------------------------------------------------------------------------------------------------
115 
116 /**
117  * Reopen the output stream after restart and reload data.
118  */
120 {
121  // Get parameters
122  const Parameters& params = Parameters::getInstance();
123 
124  mSampledTimeStep = 0;
125  if (mReduceOp == ReduceOperator::kNone) // set correct sampled timestep for raw data series
126  {
128  0 : (params.getTimeIndex() - params.getSamplingStartTimeIndex());
129  }
130 
131  // Create the memory buffer if necessary and set starting address
133 
134  // Allocate memory if needed
136 
137 
138  // Open all datasets (sizes, chunks, and attributes)
139  size_t nCuboids = mSensorMask.getDimensionSizes().ny;
140  mCuboidsInfo.reserve(nCuboids);
141  size_t actualPositionInBuffer = 0;
142 
143  // Open the HDF5 group
145 
146  for (size_t cuboidIdx = 0; cuboidIdx < nCuboids; cuboidIdx++)
147  {
148  CuboidInfo cuboidInfo;
149 
150  // Indexed from 1
151  const std::string datasetName = std::to_string(cuboidIdx + 1);
152 
153  // open the dataset
154  cuboidInfo.cuboidId = mFile.openDataset(mGroup, datasetName);
155  cuboidInfo.startingPossitionInBuffer = actualPositionInBuffer;
156  mCuboidsInfo.push_back(cuboidInfo);
157 
158  // read only if there is anything to read
159  if (params.getTimeIndex() > params.getSamplingStartTimeIndex())
160  {
162  { // Reload data
163  DimensionSizes cuboidSize((mSensorMask.getBottomRightCorner(cuboidIdx) -
164  mSensorMask.getTopLeftCorner(cuboidIdx)).nx,
165  (mSensorMask.getBottomRightCorner(cuboidIdx) -
166  mSensorMask.getTopLeftCorner(cuboidIdx)).ny,
167  (mSensorMask.getBottomRightCorner(cuboidIdx) -
168  mSensorMask.getTopLeftCorner(cuboidIdx)).nz);
169 
171  datasetName,
172  cuboidSize,
173  mStoreBuffer + actualPositionInBuffer);
174  }
175  }
176  // move the pointer for the next cuboid beginning (this inits the locations)
177  actualPositionInBuffer += (mSensorMask.getBottomRightCorner(cuboidIdx) -
178  mSensorMask.getTopLeftCorner(cuboidIdx)).nElements();
179  }
180 }// end of reopen
181 //----------------------------------------------------------------------------------------------------------------------
182 
183 /**
184  * Sample data into buffer and apply reduction, or flush to disk - based on a sensor mask.
185  */
187 {
188  switch (mReduceOp)
189  {
190  case ReduceOperator::kNone :
191  {
192  /* We use here direct HDF5 offload using MEMSPACE - seems to be faster for bigger datasets*/
193  DimensionSizes datasetPosition(0, 0, 0, 0); //4D position in the dataset
194  DimensionSizes cuboidSize(0, 0, 0, 0); // Size of the cuboid
195 
196  datasetPosition.nt = mSampledTimeStep;
197 
198  // iterate over all cuboid to be sampled
199  for (size_t cuboidIdx = 0; cuboidIdx < mCuboidsInfo.size(); cuboidIdx++)
200  {
201  cuboidSize = mSensorMask.getBottomRightCorner(cuboidIdx) - mSensorMask.getTopLeftCorner(cuboidIdx);
202  cuboidSize.nt = 1;
203 
204  mFile.writeCuboidToHyperSlab(mCuboidsInfo[cuboidIdx].cuboidId,
205  datasetPosition,
206  mSensorMask.getTopLeftCorner(cuboidIdx), // position in the SourceMatrix
207  cuboidSize,
210  }
211  mSampledTimeStep++; // Move forward in time
212 
213  break;
214 
215  // At the time being, this version using manual data lining up seems to be slower, and is not used.
216  // sampleAggregated<ReduceOperator::kNone>();
217  }// case kNone
218 
220  {
221  sampleAggregated<ReduceOperator::kRms>();
222  break;
223  }
225  {
226  sampleAggregated<ReduceOperator::kMax>();
227  break;
228  }
230  {
231  sampleAggregated<ReduceOperator::kMin>();
232  break;
233  }
234  }// switch
235 }// end of sample
236 //----------------------------------------------------------------------------------------------------------------------
237 
238 /**
239  * Apply post-processing on the buffer and flush it to the file.
240  */
242 {
243  // run inherited method
245  // When no reduction operator is applied, the data is flushed after every time step
247 }// end of postProcess
248 //----------------------------------------------------------------------------------------------------------------------
249 
250 /**
251  * Checkpoint the stream.
252  */
254 {
255  // raw data has already been flushed, others has to be flushed here
257 }// end of checkpoint
258 //----------------------------------------------------------------------------------------------------------------------
259 
260 /**
261  * Close stream (apply post-processing if necessary, flush data, close datasets and the group).
262  */
264 {
265  // the group is still open
266  if (mGroup != H5I_BADID)
267  {
268  // Close all datasets and the group
269  for (size_t cuboidIdx = 0; cuboidIdx < mCuboidsInfo.size(); cuboidIdx++)
270  {
271  mFile.closeDataset(mCuboidsInfo[cuboidIdx].cuboidId);
272  }
273  mCuboidsInfo.clear();
274 
276  mGroup = H5I_BADID;
277  }// if opened
278 }// end of close
279 //----------------------------------------------------------------------------------------------------------------------
280 
281 
282 //--------------------------------------------------------------------------------------------------------------------//
283 //------------------------------------------------ Protected methods -------------------------------------------------//
284 //--------------------------------------------------------------------------------------------------------------------//
285 
286 /**
287  * Create a new dataset for a given cuboid specified by index (order).
288  */
289 hid_t CuboidOutputStream::createCuboidDataset(const size_t cuboidIdx)
290 {
292 
293  // if time series then Number of steps else 1
294  size_t nSampledTimeSteps = (mReduceOp == ReduceOperator::kNone)
295  ? params.getNt() - params.getSamplingStartTimeIndex()
296  : 0; // will be a 3D dataset
297  // Set cuboid dimensions (subtract two corners (add 1) and use the appropriate component)
298  DimensionSizes cuboidSize((mSensorMask.getBottomRightCorner(cuboidIdx) - mSensorMask.getTopLeftCorner(cuboidIdx)).nx,
299  (mSensorMask.getBottomRightCorner(cuboidIdx) - mSensorMask.getTopLeftCorner(cuboidIdx)).ny,
300  (mSensorMask.getBottomRightCorner(cuboidIdx) - mSensorMask.getTopLeftCorner(cuboidIdx)).nz,
301  nSampledTimeSteps);
302 
303  // Set chunk size
304  // If the size of the cuboid is bigger than 32 MB per timestep, set the chunk to approx 4MB
305  size_t nSlabs = 1; //at least one slab
306  DimensionSizes cuboidChunkSize(cuboidSize.nx,
307  cuboidSize.ny,
308  cuboidSize.nz,
309  (mReduceOp == ReduceOperator::kNone) ? 1 : 0);
310 
311  if (cuboidChunkSize.nElements() > (kChunkSize4MB * 8))
312  {
313  while (nSlabs * cuboidSize.nx * cuboidSize.ny < kChunkSize4MB) nSlabs++;
314  cuboidChunkSize.nz = nSlabs;
315  }
316 
317  // Indexed from 1
318  const std::string datasetName = std::to_string(cuboidIdx + 1);
319 
320  hid_t dataset = mFile.createDataset(mGroup,
321  datasetName,
322  cuboidSize,
323  cuboidChunkSize,
325  params.getCompressionLevel());
326 
327  // Write dataset parameters
330 
331  return dataset;
332 }//end of createCuboidDataset
333 //----------------------------------------------------------------------------------------------------------------------
334 
335 /**
336  * Sample aggregated values.
337  */
338 template<BaseOutputStream::ReduceOperator reduceOp>
340 {
341  const size_t slabSize = mSourceMatrix.getDimensionSizes().ny * mSourceMatrix.getDimensionSizes().nx;
342  const size_t rowSize = mSourceMatrix.getDimensionSizes().nx;
343 
344  const float* sourceData = mSourceMatrix.getData();
345 
346  size_t cuboidInBufferStart = 0;
347 
348  // Parallelise within the cuboid - Since a typical number of cuboids is 1, than we have to paralelise inside
349  #pragma omp parallel
350  for (size_t cuboidIdx = 0; cuboidIdx < mSensorMask.getDimensionSizes().ny; cuboidIdx++)
351  {
352  const DimensionSizes topLeftCorner = mSensorMask.getTopLeftCorner(cuboidIdx);
353  const DimensionSizes bottomRightCorner = mSensorMask.getBottomRightCorner(cuboidIdx);
354 
355  size_t cuboidSlabSize = (bottomRightCorner.ny - topLeftCorner.ny + 1) *
356  (bottomRightCorner.nx - topLeftCorner.nx + 1);
357  size_t cuboidRowSize = (bottomRightCorner.nx - topLeftCorner.nx + 1);
358 
359  #pragma omp for collapse(3)
360  for (size_t z = topLeftCorner.nz; z <= bottomRightCorner.nz; z++)
361  for (size_t y = topLeftCorner.ny; y <= bottomRightCorner.ny; y++)
362  for (size_t x = topLeftCorner.nx; x <= bottomRightCorner.nx; x++)
363  {
364  const size_t storeBufferIndex = cuboidInBufferStart +
365  (z - topLeftCorner.nz) * cuboidSlabSize +
366  (y - topLeftCorner.ny) * cuboidRowSize +
367  (x - topLeftCorner.nx);
368 
369  const size_t sourceIndex = z * slabSize + y * rowSize + x;
370 
371  // based on template parameter
372  switch (reduceOp)
373  {
375  {
376  mStoreBuffer[storeBufferIndex] = sourceData[sourceIndex];
377  break;
378  }
380  {
381  mStoreBuffer[storeBufferIndex] += (sourceData[sourceIndex] * sourceData[sourceIndex]);
382  break;
383  }
385  {
386  mStoreBuffer[storeBufferIndex] = std::max(mStoreBuffer[storeBufferIndex], sourceData[sourceIndex]);
387  break;
388  }
390  {
391  mStoreBuffer[storeBufferIndex] = std::min(mStoreBuffer[storeBufferIndex], sourceData[sourceIndex]);
392  break;
393  }
394  }// switch
395  }
396  // must be done only once
397  #pragma omp single
398  {
399  cuboidInBufferStart += (bottomRightCorner - topLeftCorner).nElements();
400  }
401  }
402 }// end of sampleAggregated
403 //----------------------------------------------------------------------------------------------------------------------
404 
405 /**
406  * Flush the buffer to the file (to multiple datasets if necessary).
407  */
409 {
410  DimensionSizes position (0, 0, 0, 0);
411  DimensionSizes blockSize(0, 0, 0, 0);
412 
414 
415  for (size_t cuboidIdx = 0; cuboidIdx < mCuboidsInfo.size(); cuboidIdx++)
416  {
417  blockSize = mSensorMask.getBottomRightCorner(cuboidIdx) - mSensorMask.getTopLeftCorner(cuboidIdx);
418  blockSize.nt = 1;
419 
420  mFile.writeHyperSlab(mCuboidsInfo[cuboidIdx].cuboidId,
421  position,
422  blockSize,
423  mStoreBuffer + mCuboidsInfo[cuboidIdx].startingPossitionInBuffer);
424  }
425 
427 }// end of flushBufferToFile
428 //----------------------------------------------------------------------------------------------------------------------
429 
430 //--------------------------------------------------------------------------------------------------------------------//
431 //------------------------------------------------- Private methods --------------------------------------------------//
432 //--------------------------------------------------------------------------------------------------------------------//
hid_t openDataset(const hid_t parentGroup, MatrixName &datasetName)
Open a dataset at a specified place in the file tree.
Definition: Hdf5File.cpp:223
Calculate root mean square.
The class for real matrices.
Definition: RealMatrix.h:47
virtual void freeMemory()
Free memory.
std::vector< CuboidInfo > mCuboidsInfo
vector keeping handles and positions of all cuboids.
virtual ~CuboidOutputStream()
Destructor.
virtual void flushBufferToFile()
Flush the buffer to the file.
size_t getCompressionLevel() const
Get compression level.
Definition: Parameters.h:127
std::string mRootObjectName
HDF5 group/dataset in the output file where to store data in.
hid_t getRootGroup() const
Get handle to the root group of the file.
Definition: Hdf5File.h:608
CuboidOutputStream()=delete
Default constructor not allowed.
size_t nz
Number of elements in the z direction.
size_t getNt() const
Get total number of time steps.
Definition: Parameters.h:203
hid_t openGroup(const hid_t parentGroup, MatrixName &groupName)
Open a HDF5 group at a specified place in the file tree.
Definition: Hdf5File.cpp:196
const IndexMatrix & mSensorMask
Sensor mask to sample data.
const ReduceOperator mReduceOp
Reduction operator.
void writeCuboidToHyperSlab(const hid_t dataset, const DimensionSizes &hyperslabPosition, const DimensionSizes &cuboidPosition, const DimensionSizes &cuboidSize, const DimensionSizes &matrixDimensions, const float *matrixData)
Write a cuboid selected within the matrixData into a hyperslab.
Definition: Hdf5File.cpp:431
virtual void sample()
Sample data into buffer and apply reduction, or flush to disk - based on a sensor mask...
Class storing all parameters of the simulation.
Definition: Parameters.h:50
virtual void allocateMemory()
Allocate memory using proper memory alignment.
virtual void postProcess()
Apply post-processing on the buffer and flush it to the file.
Store actual data (time series).
void sampleAggregated()
Sample aggregated values.
virtual void close()
Close stream (apply post-processing if necessary, flush data and close).
The header file containing the parameters of the simulation.
void writeMatrixDomainType(const hid_t parentGroup, MatrixName &datasetName, const MatrixDomainType &matrixDomainType)
Write matrix data type into the dataset at a specified place in the file tree.
Definition: Hdf5File.cpp:807
void closeDataset(const hid_t dataset)
Close dataset.
Definition: Hdf5File.cpp:325
size_t nt
Number of time steps (for time series datasets).
void writeMatrixDataType(const hid_t parentGroup, MatrixName &datasetName, const MatrixDataType &matrixDataType)
Write matrix data type into the dataset at a specified place in the file tree.
Definition: Hdf5File.cpp:793
DimensionSizes getTopLeftCorner(const size_t &index) const
Get the top left corner of the index-th cuboid.
hid_t mGroup
Handle to a HDF5 dataset.
size_t getSamplingStartTimeIndex() const
Get start time index when sensor data collection begins.
Definition: Parameters.h:499
hid_t createGroup(const hid_t parentGroup, MatrixName &groupName)
Create a HDF5 group at a specified place in the file tree.
Definition: Hdf5File.cpp:178
The header file of classes responsible for storing output quantities based on the cuboid sensor mask ...
virtual const DimensionSizes & getDimensionSizes() const
Get dimension sizes of the matrix.
Class wrapping the HDF5 routines.
Definition: Hdf5File.h:490
hid_t cuboidId
Id of the dataset storing the given cuboid.
size_t mBufferSize
Buffer size.
void writeHyperSlab(const hid_t dataset, const DimensionSizes &position, const DimensionSizes &size, const T *data)
Write a hyperslab into the dataset.
Definition: Hdf5File.cpp:335
const std::string MatrixName
Datatype for matrix names.
Definition: MatrixNames.h:39
Structure with 4D dimension sizes (3 in space and 1 in time).
size_t mSampledTimeStep
Timestep to store (N/A for aggregated).
virtual void checkpoint()
Checkpoint the stream and close.
The matrix is stored in floating point 32b wide format.
Hdf5File & mFile
Handle to HDF5 output file.
size_t ny
Number of elements in the y direction.
size_t getTimeIndex() const
Get actual simulation time step.
Definition: Parameters.h:208
virtual void postProcess()
Apply post-processing on the buffer and flush it to the file.
void closeGroup(const hid_t group)
Close a group.
Definition: Hdf5File.cpp:214
virtual void create()
Create a HDF5 stream and allocate data for it.
const RealMatrix & mSourceMatrix
Source matrix to be sampled.
size_t startingPossitionInBuffer
Having a single buffer for all cuboids, where this one starts.
DimensionSizes getBottomRightCorner(const size_t &index) const
Get the top bottom right of the index-th cuboid.
size_t nElements() const
Get the number of elements, in 3D only spatial domain, in 4D with time.
float * mStoreBuffer
Temporary buffer for store - only if Buffer Reuse = false!
ReduceOperator
How to aggregate data.
virtual float * getData()
Get raw data out of the class (for direct kernel access).
void readCompleteDataset(const hid_t parentGroup, MatrixName &datasetName, const DimensionSizes &dimensionSizes, T *data)
Read data from the dataset at a specified place in the file tree.
Definition: Hdf5File.cpp:678
virtual hid_t createCuboidDataset(const size_t cuboidIdx)
Create a new dataset for a given cuboid specified by index (order).
virtual void reopen()
Reopen the output stream after restart and reload data.
size_t nx
Number of elements in the x direction.
static Parameters & getInstance()
Get instance of the singleton class.
Definition: Parameters.cpp:84
virtual const DimensionSizes & getDimensionSizes() const
Get dimension sizes of the matrix.
This structure information about a HDF5 dataset (one cuboid).
The class for 64b unsigned integers (indices). It is used for linear and cuboid corners masks to get ...
Definition: IndexMatrix.h:47
hid_t createDataset(const hid_t parentGroup, MatrixName &datasetName, const DimensionSizes &dimensionSizes, const DimensionSizes &chunkSizes, const MatrixDataType matrixDataType, const size_t compressionLevel)
Create a float HDF5 dataset at a specified place in the file tree (3D/4D).
Definition: Hdf5File.cpp:241
static constexpr size_t kChunkSize4MB
chunk size of 4MB in number of float elements.
Abstract base class for output data streams (sampled data).
The matrix is defined on real domain.
bool mBufferReuse
if true, the container reuses another matrix as scratch place, e.g. Temp_1_RS3D, Temp_2_RS3D, Temp_3_RS3D.
size_t getSizeOfAllCuboids() const
Get total number of elements in all cuboids to be able to allocate output file.