Skip to content

Commit d9b0c59

Browse files
ekapadiKort Travis
andauthored
Fix for slab size to data size mismatch, on write of ragged workspaces. (#39094)
* Fix SEGFAULT in 'NexusFileIO::writeNexusProcessedData2D'. This commit includes the following changes: * Fix for slab size to data size mismatch, on write of ragged workspaces; * Cleanup of duplicate code sections in 'writeNexusProcessedData2D'; * Fix some conditional logic in 'writeNexusProcessedData2D'. * Add ragged readback test. --------- Co-authored-by: Kort Travis <traviska@ornl.gov>
1 parent 379b6e1 commit d9b0c59

File tree

5 files changed

+260
-81
lines changed

5 files changed

+260
-81
lines changed

Framework/DataHandling/inc/MantidDataHandling/SaveNexusProcessedHelper.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ namespace Mantid {
3030
namespace NeXus {
3131
/** @class NexusFileIO SaveNexusProcessedHelper.h NeXus/SaveNexusProcessedHelper.h
3232
33-
Utility method for saving NeXus format of Mantid Workspace
33+
Utility methods for saving Mantid Workspaces in NeXus format.
3434
This class interfaces to the C Nexus API. This is written for use by
3535
Save and Load NexusProcessed classes, though it could be extended to
3636
other Nexus formats. It might be replaced in future by methods using

Framework/DataHandling/src/SaveNexusProcessedHelper.cpp

Lines changed: 107 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,30 @@
1515
// Maximum base file name size on modern windows systems is 260 characters
1616
#define NAME_MAX 260
1717
#endif /* _WIN32 */
18+
1819
#include "MantidAPI/NumericAxis.h"
1920
#include "MantidDataHandling/SaveNexusProcessedHelper.h"
2021
#include "MantidDataObjects/EventWorkspace.h"
2122
#include "MantidDataObjects/PeaksWorkspace.h"
2223
#include "MantidDataObjects/RebinnedOutput.h"
2324
#include "MantidDataObjects/TableWorkspace.h"
25+
#include "MantidHistogramData/Histogram.h"
2426

2527
#include <Poco/File.h>
2628
#include <Poco/Path.h>
29+
#include <memory>
2730

2831
namespace Mantid::NeXus {
2932
using namespace Kernel;
3033
using namespace API;
3134
using namespace DataObjects;
3235

36+
using HistogramData::HistogramDx;
37+
using HistogramData::HistogramE;
38+
using HistogramData::HistogramX;
39+
using HistogramData::HistogramY;
40+
using Mantid::MantidVec; // for `<rebinned output>->readF(size_t index)`
41+
3342
namespace {
3443
/// static logger
3544
Logger g_log("NexusFileIO");
@@ -189,6 +198,79 @@ int NexusFileIO::writeNexusProcessedHeader(const std::string &title, const std::
189198
return (0);
190199
}
191200

201+
//-------------------------------------------------------------------------------------
202+
// Internal constants and template functions, used by `NexusFileIO::writeNexusProcessedData2D`:
203+
204+
namespace {
205+
206+
const double _DEFAULT_FILL_VALUE(0.0);
207+
208+
// Typedef for vector-accessor member functions with signatures like:
209+
// `const HistogramData::HistogramY & Mantid::API::MatrixWorkspace::y ( const size_t index )
210+
// const`
211+
template <class V, class WS> using _VAccessor = const V &(WS::*)(size_t) const;
212+
213+
// Return the data pointer for a vector:
214+
// this allows us to work with both `MantidVec` and `FixedLengthVector`.
215+
template <class V> const double *_dataPointer(const V &v) { return v.rawData().data(); }
216+
217+
template <> const double *_dataPointer<MantidVec>(const MantidVec &v) { return v.data(); }
218+
219+
// Internal-use method:
220+
// * Create the dataset and write chunks of double-precision data;
221+
// * Optionally fill the chunks with a specified fill value;
222+
// * Optionally close the dataset.
223+
template <class V, class WS>
224+
void _writeChunkedData(std::shared_ptr<::NeXus::File> dest, // Must have open group, but NO open dataset
225+
const std::string &name,
226+
std::shared_ptr<const WS> src, // Do not pass std::shared_ptr<..> by reference!
227+
const std::vector<int> &indices, _VAccessor<V, WS> vData, bool raggedSpectra = false,
228+
::NeXus::NXcompression compressionType = ::NeXus::NXcompression::NONE,
229+
double fillValue = _DEFAULT_FILL_VALUE, bool closeData = true) {
230+
231+
const size_t N_chunk = indices.size(); // number of spectra
232+
233+
size_t _chunk_size = ((*src).*vData)(0).size();
234+
if (raggedSpectra) {
235+
for (size_t n : indices)
236+
_chunk_size = std::max(_chunk_size, ((*src).*vData)(n).size());
237+
}
238+
const size_t chunk_size = _chunk_size;
239+
240+
const ::NeXus::DimVector dims = {static_cast<::NeXus::dimsize_t>(N_chunk),
241+
static_cast<::NeXus::dimsize_t>(chunk_size)};
242+
const ::NeXus::DimSizeVector chunk_dims = {1, static_cast<::NeXus::dimsize_t>(chunk_size)};
243+
244+
// Create and open the dataset.
245+
// (If compressionType == NXcompression::NONE, this just creates a non-compressed dataset.)
246+
dest->makeCompData(name, NXnumtype::FLOAT64, dims, compressionType, chunk_dims, true);
247+
248+
// Prepare a padding vector. (Unfortunately, NeXus-api does not access `setFillValue`.)
249+
const bool pad_data(fillValue != _DEFAULT_FILL_VALUE);
250+
const std::vector<double> vFill(pad_data ? chunk_size : 0, fillValue);
251+
252+
// Write the data.
253+
::NeXus::DimVector start{0, 0};
254+
for (size_t n : indices) {
255+
const auto &v = ((*src).*vData)(n);
256+
const ::NeXus::DimSizeVector data_dims = {1, static_cast<::NeXus::dimsize_t>(v.size())};
257+
dest->putSlab(_dataPointer<V>(v), start, data_dims);
258+
if (pad_data && data_dims[1] != static_cast<::NeXus::dimsize_t>(chunk_size)) {
259+
// Fill the remainder of the slab.
260+
const ::NeXus::DimSizeVector fill_dims = {1, static_cast<::NeXus::dimsize_t>(chunk_size) - data_dims[1]};
261+
const ::NeXus::DimVector _start = {start[0], data_dims[1]};
262+
dest->putSlab(vFill.data(), _start, fill_dims);
263+
}
264+
265+
++start[0];
266+
}
267+
268+
if (closeData)
269+
dest->closeData();
270+
}
271+
272+
} // namespace
273+
192274
//-------------------------------------------------------------------------------------
193275
/** Write out a MatrixWorkspace's data as a 2D matrix.
194276
* Use writeNexusProcessedDataEvent if writing an EventWorkspace.
@@ -210,13 +292,6 @@ int NexusFileIO::writeNexusProcessedData2D(const API::MatrixWorkspace_const_sptr
210292
const size_t nHist = localworkspace->getNumberHistograms();
211293
if (nHist < 1)
212294
return (2);
213-
const size_t nSpect = indices.size();
214-
size_t nSpectBins = localworkspace->y(0).size();
215-
if (raggedSpectra)
216-
for (size_t i = 0; i < nSpect; i++)
217-
nSpectBins = std::max(nSpectBins, localworkspace->y(indices[i]).size());
218-
::NeXus::DimVector dims_array = {static_cast<::NeXus::dimsize_t>(nSpect),
219-
static_cast<::NeXus::dimsize_t>(nSpectBins)};
220295

221296
// Set the axis labels and values
222297
Mantid::API::Axis *xAxis = localworkspace->getAxis(0);
@@ -240,24 +315,21 @@ int NexusFileIO::writeNexusProcessedData2D(const API::MatrixWorkspace_const_sptr
240315
}
241316
// Get the values on the vertical axis
242317
std::vector<double> axis2;
318+
const size_t nSpect = indices.size();
243319
if (nSpect < nHist)
244320
for (size_t i = 0; i < nSpect; i++)
245321
axis2.emplace_back((*sAxis)(indices[i]));
246322
else
247323
for (size_t i = 0; i < sAxis->length(); i++)
248324
axis2.emplace_back((*sAxis)(i));
249325

250-
::NeXus::DimVector start = {0, 0};
251-
::NeXus::DimSizeVector asize = {1, dims_array[1]};
252-
253326
// -------------- Actually write the 2D data ----------------------------
254327
if (write2Ddata) {
255-
std::string name = "values";
256-
m_filehandle->makeCompData(name, NXnumtype::FLOAT64, dims_array, m_nexuscompression, asize, true);
257-
for (size_t i = 0; i < nSpect; i++) {
258-
m_filehandle->putSlab(localworkspace->y(indices[i]).rawData().data(), start, asize);
259-
start[0]++;
260-
}
328+
_writeChunkedData<HistogramY, MatrixWorkspace>(m_filehandle, "values", localworkspace, indices, &MatrixWorkspace::y,
329+
raggedSpectra, m_nexuscompression, _DEFAULT_FILL_VALUE,
330+
false // don't close the dataset
331+
);
332+
261333
if (m_progress != nullptr)
262334
m_progress->reportIncrement(1, "Writing data");
263335
m_filehandle->putAttr("signal", 1);
@@ -267,28 +339,21 @@ int NexusFileIO::writeNexusProcessedData2D(const API::MatrixWorkspace_const_sptr
267339
m_filehandle->putAttr("unit_label", localworkspace->YUnitLabel(), false);
268340
m_filehandle->closeData();
269341

270-
// error
271-
name = "errors";
272-
m_filehandle->makeCompData(name, NXnumtype::FLOAT64, dims_array, m_nexuscompression, asize, true);
273-
start[0] = 0;
274-
for (size_t i = 0; i < nSpect; i++) {
275-
m_filehandle->putSlab(localworkspace->e(indices[i]).rawData().data(), start, asize);
276-
start[0]++;
277-
}
278-
342+
// errors
343+
_writeChunkedData<HistogramE, MatrixWorkspace>(m_filehandle, "errors", localworkspace, indices, &MatrixWorkspace::e,
344+
raggedSpectra, m_nexuscompression);
279345
if (m_progress != nullptr)
280346
m_progress->reportIncrement(1, "Writing data");
281347

282348
// Fractional area for RebinnedOutput
283349
if (localworkspace->id() == "RebinnedOutput") {
284350
RebinnedOutput_const_sptr rebin_workspace = std::dynamic_pointer_cast<const RebinnedOutput>(localworkspace);
285-
name = "frac_area";
286-
m_filehandle->makeCompData(name, NXnumtype::FLOAT64, dims_array, m_nexuscompression, asize, true);
287-
start[0] = 0;
288-
for (size_t i = 0; i < nSpect; i++) {
289-
m_filehandle->putSlab(rebin_workspace->readF(indices[i]).data(), start, asize);
290-
start[0]++;
291-
}
351+
352+
_writeChunkedData<MantidVec, RebinnedOutput>(m_filehandle, "frac_area", rebin_workspace, indices,
353+
&RebinnedOutput::readF, raggedSpectra, m_nexuscompression,
354+
_DEFAULT_FILL_VALUE,
355+
false // don't close the dataset
356+
);
292357

293358
std::string finalized = (rebin_workspace->isFinalized()) ? "1" : "0";
294359
m_filehandle->putAttr("finalized", finalized);
@@ -297,65 +362,28 @@ int NexusFileIO::writeNexusProcessedData2D(const API::MatrixWorkspace_const_sptr
297362

298363
if (m_progress != nullptr)
299364
m_progress->reportIncrement(1, "Writing data");
365+
m_filehandle->closeData();
300366
}
301367

302-
// Potentially x error
368+
// x errors
303369
if (localworkspace->hasDx(0)) {
304-
dims_array[0] = static_cast<int>(nSpect);
305-
dims_array[1] = static_cast<int>(localworkspace->dx(0).size());
306-
std::string dxErrorName = "xerrors";
307-
m_filehandle->makeCompData(dxErrorName, NXnumtype::FLOAT64, dims_array, m_nexuscompression, asize, true);
308-
start[0] = 0;
309-
asize[1] = dims_array[1];
310-
for (size_t i = 0; i < nSpect; i++) {
311-
m_filehandle->putSlab(localworkspace->dx(indices[i]).rawData().data(), start, asize);
312-
start[0]++;
313-
}
370+
_writeChunkedData<HistogramDx, MatrixWorkspace>(m_filehandle, "xerrors", localworkspace, indices,
371+
&MatrixWorkspace::dx, raggedSpectra, m_nexuscompression);
314372
}
315-
316-
m_filehandle->closeData();
317373
}
318374

319375
// write X data, as single array or all values if "ragged"
320-
if (raggedSpectra) {
321-
size_t max_x_size{0};
322-
for (size_t i = 0; i < nSpect; i++)
323-
max_x_size = std::max(max_x_size, localworkspace->x(indices[i]).size());
324-
dims_array[0] = static_cast<int>(nSpect);
325-
dims_array[1] = static_cast<int>(max_x_size);
326-
m_filehandle->makeData("axis1", NXnumtype::FLOAT64, dims_array, true);
327-
start[0] = 0;
328-
329-
// create vector of NaNs to fill invalid space at end of ragged array
330-
std::vector<double> nans(max_x_size, std::numeric_limits<double>::quiet_NaN());
331-
332-
for (size_t i = 0; i < nSpect; i++) {
333-
size_t nBins = localworkspace->x(indices[i]).size();
334-
asize[1] = static_cast<int>(nBins);
335-
m_filehandle->putSlab(localworkspace->x(indices[i]).rawData().data(), start, asize);
336-
if (nBins < max_x_size) {
337-
start[1] = asize[1];
338-
asize[1] = static_cast<int>(max_x_size - nBins);
339-
m_filehandle->putSlab(nans.data(), start, asize);
340-
start[1] = 0;
341-
}
342-
start[0]++;
343-
}
344-
} else if (uniformSpectra) {
376+
if (uniformSpectra) {
345377
m_filehandle->makeData("axis1", NXnumtype::FLOAT64,
346378
::NeXus::DimVector{static_cast<::NeXus::dimsize_t>(localworkspace->x(0).size())}, true);
347379
m_filehandle->putData(localworkspace->x(0).rawData().data());
348380

349381
} else {
350-
dims_array[0] = static_cast<int>(nSpect);
351-
dims_array[1] = static_cast<int>(localworkspace->x(0).size());
352-
m_filehandle->makeData("axis1", NXnumtype::FLOAT64, dims_array, true);
353-
start[0] = 0;
354-
asize[1] = dims_array[1];
355-
for (size_t i = 0; i < nSpect; i++) {
356-
m_filehandle->putSlab(localworkspace->x(indices[i]).rawData().data(), start, asize);
357-
start[0]++;
358-
}
382+
_writeChunkedData<HistogramX, MatrixWorkspace>(
383+
m_filehandle, "axis1", localworkspace, indices, &MatrixWorkspace::x, raggedSpectra,
384+
::NeXus::NXcompression::NONE, raggedSpectra ? std::numeric_limits<double>::quiet_NaN() : _DEFAULT_FILL_VALUE,
385+
false // don't close the dataset
386+
);
359387
}
360388

361389
std::string dist = (localworkspace->isDistribution()) ? "1" : "0";

0 commit comments

Comments
 (0)