Mantid
Loading...
Searching...
No Matches
SaveNXSPE.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
8
12#include "MantidAPI/Run.h"
15
18#include "MantidNexus/NexusFile.h"
19
22#include "MantidKernel/Unit.h"
23
24#include <boost/scoped_array.hpp>
25#include <limits>
26
27namespace Mantid::DataHandling {
28
29// Register the algorithm into the algorithm factory
30DECLARE_ALGORITHM(SaveNXSPE)
31
32using namespace Kernel;
33using namespace API;
34
35const double SaveNXSPE::MASK_FLAG = std::numeric_limits<double>::quiet_NaN();
36const double SaveNXSPE::MASK_ERROR = 0.0;
37// works fine but there were cases that some compilers crush on this (VS2008 in
38// mixed .net environment ?)
39const std::string SaveNXSPE::NXSPE_VER = "1.3";
40// 4MB chunk size
41const size_t SaveNXSPE::MAX_CHUNK_SIZE = 4194304;
42
44
49 auto wsValidator = std::make_shared<CompositeValidator>();
50 wsValidator->add(std::make_shared<API::WorkspaceUnitValidator>("DeltaE"));
51 wsValidator->add<API::CommonBinsValidator>();
52 wsValidator->add<API::HistogramValidator>();
53
55 std::make_unique<WorkspaceProperty<MatrixWorkspace>>("InputWorkspace", "", Direction::Input, wsValidator),
56 "The name of the workspace to save.");
57
59 std::make_unique<API::FileProperty>("Filename", "", FileProperty::Save, std::vector<std::string>(1, ".nxspe")),
60 "The name of the NXSPE file to write, as a full or relative path");
61
62 declareProperty("Efixed", EMPTY_DBL(), "Value of the fixed energy to write into NXSPE file.");
63 declareProperty("Psi", EMPTY_DBL(), "Value of PSI to write into NXSPE file.");
64 declareProperty("KiOverKfScaling", true, "Flags in the file whether Ki/Kf scaling has been done or not.");
65
66 // optional par or phx file
67 std::vector<std::string> fileExts{".par", ".phx"};
69 std::make_unique<FileProperty>("ParFile", "not_used.par", FileProperty::OptionalLoad, fileExts),
70 "If provided, will replace detectors parameters in resulting nxspe file with the values taken from the file. \n\
71 Should be used only if the parameters, calculated by the [[FindDetectorsPar]] algorithm are not suitable for some reason. \n\
72 See [[FindDetectorsPar]] description for the details.");
73}
74
75std::map<std::string, std::string> SaveNXSPE::validateInputs() {
76 std::map<std::string, std::string> issues;
77 std::string inputWS = getPropertyValue("InputWorkspace");
78 if (inputWS.find("/") != std::string::npos) {
79 issues["InputWorkspace"] = "The input workspace name cannot contain a '/' character.";
80 }
81 return issues;
82}
83
88 // Retrieve the input workspace
89 MatrixWorkspace_sptr inputWS = getProperty("InputWorkspace");
90
91 // Number of spectra
92 const auto nHist = inputWS->getNumberHistograms();
93 // Number of energy bins
94 const auto nBins = inputWS->blocksize();
95
96 // Retrieve the filename from the properties
97 std::string filename = getPropertyValue("Filename");
98
99 // Create the file.
100 Nexus::File nxFile(filename, NXaccess::CREATE5);
101
102 // Make the top level entry (and open it)
103 std::string entryName = getPropertyValue("InputWorkspace");
104 if (entryName.empty()) {
105 entryName = "mantid_workspace";
106 }
107 nxFile.makeGroup(entryName, "NXentry", true);
108
109 // Definition name and version
110 nxFile.writeData("definition", "NXSPE");
111 nxFile.openData("definition");
112 nxFile.putAttr("version", NXSPE_VER);
113 nxFile.closeData();
114
115 // Program name and version
116 nxFile.writeData("program_name", "mantid");
117 nxFile.openData("program_name");
118 nxFile.putAttr("version", Mantid::Kernel::MantidVersion::version());
119 nxFile.closeData();
120
121 // Create NXSPE_info
122 nxFile.makeGroup("NXSPE_info", "NXcollection", true);
123
124 // Get the value out of the property first
125 double efixed = getProperty("Efixed");
126 bool efixed_provided(!isEmpty(efixed));
127 if (!efixed_provided) {
129 }
130 auto eMode = inputWS->getEMode();
131 bool write_single_energy(true);
132 if (!efixed_provided) {
133 // efixed identified differently for different types of inelastic instruments
134 // Now lets check to see if we can retrieve energy from workspace.
135 switch (eMode) {
137 // here workspace detectors should always have the energy
138 auto detEfixed = getIndirectEfixed(inputWS);
139 if (detEfixed.size() == 1) {
140 efixed = detEfixed[0];
141 } else if (detEfixed.size() > 1) {
142 write_single_energy = false; // do not write single energy, write array of energies here
143 nxFile.writeData("fixed_energy", detEfixed);
144 }
145 if (!write_single_energy || (detEfixed[0] > std::numeric_limits<float>::epsilon()))
146 // this is generally incorrect, but following historical practice
147 break; // assume that indirect instrument without energy attached to detectors
148 // may have energy specified in Ei log. Some user scripts may depend on it.
149 // so here we break only if Ei is defined on detectors and look for
150 // Ei log otherwise
151 }
152 case (Kernel::DeltaEMode::Elastic): // no efixed for elastic,
153 // whatever retrieved from the property should remain unchanged.
154 // Despite it is incorrect, previous tests were often using Instrument in
155 // Elastic mode as the source of data for Direct inelastic. Despite correct
156 // action would be no efixed for elastic, to keep tests happy here we try to
157 // derive efixed from Ei log similarly to Direct inelastic case if no external
158 // efixed provided to the algorithm.
159 case (Kernel::DeltaEMode::Undefined): // This should not happen
160 eMode = Kernel::DeltaEMode::Direct; // but to keep cpp-check happy and code consistent, set up Direct
161 // instrument in this case.
163 const API::Run &run = inputWS->run();
164 if (run.hasProperty("Ei")) {
165 efixed = run.getPropertyValueAsType<double>("Ei");
166 }
167 break;
168 }
169 }
170 }
171 if (write_single_energy) // if multiple energies, they have already been written
172 nxFile.writeData("fixed_energy", efixed);
173
174 nxFile.openData("fixed_energy");
175 nxFile.putAttr("units", "meV");
176 nxFile.closeData();
177
178 double psi = getProperty("Psi");
179 if (isEmpty(psi))
180 psi = MASK_FLAG;
181 nxFile.writeData("psi", psi);
182 nxFile.openData("psi");
183 nxFile.putAttr("units", "degrees");
184 nxFile.closeData();
185
186 bool kikfScaling = getProperty("KiOverKfScaling");
187 if (kikfScaling) {
188 nxFile.writeData("ki_over_kf_scaling", 1);
189 } else {
190 nxFile.writeData("ki_over_kf_scaling", 0);
191 }
192
193 nxFile.closeGroup(); // NXSPE_info
194
195 // NXinstrument
196 nxFile.makeGroup("instrument", "NXinstrument", true);
197 // Write the instrument name
198 nxFile.writeData("name", inputWS->getInstrument()->getName());
199 // and the short name
200 nxFile.openData("name");
201 // TODO: Get the instrument short name
202 nxFile.putAttr("short_name", inputWS->getInstrument()->getName());
203 nxFile.closeData();
204 nxFile.writeData("run_number", inputWS->getRunNumber());
205
206 if (eMode == Kernel::DeltaEMode::Direct ||
207 eMode == Kernel::DeltaEMode::Elastic) { // Elastic is also not entirely correct but
208 // to maintain consistency with previous code, assume Direct instrument in Elastic mode.
209 // NXfermi_chopper
210 nxFile.makeGroup("fermi", "NXfermi_chopper", true);
211 nxFile.writeData("energy", efixed);
212 nxFile.closeGroup(); // NXfermi_chopper
213 } // TODO: Do not know what indirect people want for their instrument,
214 // but they certainly do not have Fermi chopper.
215 // This is for them to decide what(if) they want here in a future.
216
217 nxFile.closeGroup(); // NXinstrument
218
219 // NXsample
220 nxFile.makeGroup("sample", "NXsample", true);
221 // TODO: Write sample info
222 // nxFile.writeData("rotation_angle", 0.0);
223 // nxFile.writeData("seblock", "NONE");
224 // nxFile.writeData("temperature", 300.0);
225
226 nxFile.closeGroup(); // NXsample
227
228 // Make the NXdata group
229 nxFile.makeGroup("data", "NXdata", true);
230
231 // Energy bins
232 // Get the Energy Axis (X) of the first spectra (they are all the same -
233 // checked above)
234 const auto &X = inputWS->x(0);
235 nxFile.writeData("energy", X.rawData());
236 nxFile.openData("energy");
237 nxFile.putAttr("units", "meV");
238 nxFile.closeData();
239
240 // let's create some blank arrays in the nexus file
241 using Dimensions = Nexus::DimVector;
242 Dimensions arrayDims{nHist, nBins};
243 nxFile.makeData("data", NXnumtype::FLOAT64, arrayDims, false);
244 nxFile.makeData("error", NXnumtype::FLOAT64, arrayDims, false);
245
246 // Add the axes attributes to the data
247 nxFile.openData("data");
248 nxFile.putAttr("signal", 1);
249 nxFile.putAttr("axes", "polar:energy");
250 nxFile.closeData();
251
252 // What size slabs are we going to write...
253 // Use an intermediate in-memory buffer to reduce the number
254 // of calls to putslab, i.e disk writes but still write whole rows
255 Dimensions slabStart(2, 0), slabSize(2, 0);
256 auto chunkRows = static_cast<Dimensions::value_type>(MAX_CHUNK_SIZE / 8 / nBins);
257 if (nHist < chunkRows) {
258 chunkRows = nHist;
259 }
260 // slab size for all but final write
261 slabSize[0] = chunkRows;
262 slabSize[1] = nBins;
263
264 // Allocate the temporary buffers for the signal and errors
265 using Buffer = boost::scoped_array<double>;
266 const size_t bufferSize(slabSize[0] * slabSize[1]);
267 Buffer signalBuffer(new double[bufferSize]);
268 Buffer errorBuffer(new double[bufferSize]);
269
270 // Write the data
271 Progress progress(this, 0.0, 1.0, nHist);
272 uint64_t bufferCounter(0);
273 const auto &spectrumInfo = inputWS->spectrumInfo();
274 for (std::size_t i = 0; i < nHist; ++i) {
275 progress.report();
276
277 double *signalBufferStart = signalBuffer.get() + bufferCounter * nBins;
278 double *errorBufferStart = errorBuffer.get() + bufferCounter * nBins;
279 if (spectrumInfo.hasDetectors(i) && !spectrumInfo.isMonitor(i)) {
280 // a detector but not a monitor
281 if (!spectrumInfo.isMasked(i)) {
282 std::copy(inputWS->y(i).cbegin(), inputWS->y(i).cend(), signalBufferStart);
283 std::copy(inputWS->e(i).cbegin(), inputWS->e(i).cend(), errorBufferStart);
284 } else {
285 std::fill_n(signalBufferStart, nBins, MASK_FLAG);
286 std::fill_n(errorBufferStart, nBins, MASK_ERROR);
287 }
288 } else {
289 // no detector gets zeroes.
290 std::fill_n(signalBufferStart, nBins, 0.0);
291 std::fill_n(errorBufferStart, nBins, 0.0);
292 }
293 ++bufferCounter;
294
295 // Do we need to flush the buffer. Either we have filled the buffer
296 // or we have reached the end of the workspace and not completely filled
297 // the buffer
298 if (bufferCounter == chunkRows || i == nHist - 1) {
299 // techincally only need to update for the very last slab but
300 // this is always correct and avoids an if statement
301 slabSize[0] = bufferCounter;
302 nxFile.openData("data");
303 nxFile.putSlab(signalBuffer.get(), slabStart, slabSize);
304 nxFile.closeData();
305
306 // Open the error
307 nxFile.openData("error");
308 nxFile.putSlab(errorBuffer.get(), slabStart, slabSize);
309 nxFile.closeData();
310
311 // Reset counters for next time
312 slabStart[0] += bufferCounter;
313 bufferCounter = 0;
314 }
315 }
316
317 // execute the algorithm to calculate the detector's parameters;
318 auto spCalcDetPar = createChildAlgorithm("FindDetectorsPar", 0, 1, true, 1);
319
320 spCalcDetPar->initialize();
321 spCalcDetPar->setProperty("InputWorkspace", inputWS);
322 std::string parFileName = this->getPropertyValue("ParFile");
323 if (!(parFileName.empty() || parFileName == "not_used.par")) {
324 spCalcDetPar->setPropertyValue("ParFile", parFileName);
325 }
326 spCalcDetPar->execute();
327
328 //
329 auto const *pCalcDetPar = dynamic_cast<FindDetectorsPar *>(spCalcDetPar.get());
330 if (!pCalcDetPar) { // "can not get pointer to FindDetectorsPar algorithm"
331 throw(std::bad_cast());
332 }
333 const std::vector<double> &azimuthal = pCalcDetPar->getAzimuthal();
334 const std::vector<double> &polar = pCalcDetPar->getPolar();
335 const std::vector<double> &azimuthal_width = pCalcDetPar->getAzimWidth();
336 const std::vector<double> &polar_width = pCalcDetPar->getPolarWidth();
337 const std::vector<double> &secondary_flightpath = pCalcDetPar->getFlightPath();
338
339 // Write the Polar (2Theta) angles
340 nxFile.writeData("polar", polar);
341
342 // Write the Azimuthal (phi) angles
343 nxFile.writeData("azimuthal", azimuthal);
344
345 // Now the widths...
346 nxFile.writeData("polar_width", polar_width);
347 nxFile.writeData("azimuthal_width", azimuthal_width);
348
349 // Secondary flight path
350 nxFile.writeData("distance", secondary_flightpath);
351
352 nxFile.closeGroup(); // NXdata
353
354 nxFile.closeGroup(); // Top level NXentry
355}
356
366std::vector<double> SaveNXSPE::getIndirectEfixed(const MatrixWorkspace_sptr &inputWS) const {
367 // Number of spectra
368 const auto nHist = static_cast<int64_t>(inputWS->getNumberHistograms());
369 const auto &spectrumInfo = inputWS->spectrumInfo();
370 Mantid::MantidVec AllEnergies(nHist);
371 size_t nDet(0);
373 Units::Time inUnit;
374 Units::DeltaE outUnit;
375 for (int64_t i = 0; i < nHist; ++i) {
376 if (spectrumInfo.hasDetectors(i) && !spectrumInfo.isMonitor(i)) {
377 // a detector but not a monitor and not masked for indirect instrument should have efixed
378 spectrumInfo.getDetectorValues(inUnit, outUnit, DeltaEMode::Indirect, true, i, pmap);
379 AllEnergies[nDet] = pmap[UnitParams::efixed];
380 nDet++;
381 }
382 }
383 AllEnergies.resize(nDet);
384 if (nDet == 0)
385 return AllEnergies; // Empty vector, no energies,
386
387 if (std::all_of(AllEnergies.begin(), AllEnergies.end(), [&AllEnergies](double energy) {
388 return std::abs(energy - AllEnergies[0]) < std::numeric_limits<float>::epsilon();
389 })) {
390 // all detectors have same energy
391 AllEnergies.resize(1);
392 }
393
394 return AllEnergies;
395}
396} // namespace Mantid::DataHandling
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:538
double energy
Definition GetAllEi.cpp:157
Base class from which all concrete algorithm classes should be derived.
Definition Algorithm.h:76
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
std::string getPropertyValue(const std::string &name) const override
Get the value of a property as a string.
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
virtual std::shared_ptr< Algorithm > createChildAlgorithm(const std::string &name, const double startProgress=-1., const double endProgress=-1., const bool enableLogging=true, const int &version=-1)
Create a Child Algorithm.
void progress(double p, const std::string &msg="", double estimatedTime=0.0, int progressPrecision=0)
Sends ProgressNotification.
static bool isEmpty(const NumT toCheck)
checks that the value was not set by users, uses the value in empty double/int.
A validator which provides a TENTATIVE check that a workspace contains common bins in each spectrum.
@ OptionalLoad
to specify a file to read but the file doesn't have to exist
@ Save
to specify a file to write to, the file may or may not exist
A validator which checks that a workspace contains histogram data (the default) or point data as requ...
bool hasProperty(const std::string &name) const
Does the property exist on the object.
HeldType getPropertyValueAsType(const std::string &name) const
Get the value of a property as the given TYPE.
Helper class for reporting progress from algorithms.
Definition Progress.h:25
This class stores information regarding an experimental run as a series of log entries.
Definition Run.h:35
A property class for workspaces.
std::vector< double > const & getAzimuthal() const
the accessors, used to return algorithm results when called as Child Algorithm, without setting the p...
static const size_t MAX_CHUNK_SIZE
The size in bytes of a chunk to accumulate to write to the file at once.
Definition SaveNXSPE.h:77
void init() override
Initialisation code.
Definition SaveNXSPE.cpp:48
static const std::string NXSPE_VER
file format version
Definition SaveNXSPE.h:75
std::vector< double > getIndirectEfixed(const Mantid::API::MatrixWorkspace_sptr &inputWS) const
Calculate fixed energy for indirect instrument.
void exec() override
Execution code.
Definition SaveNXSPE.cpp:87
static const double MASK_FLAG
Value for data if pixel is masked.
Definition SaveNXSPE.h:71
std::map< std::string, std::string > validateInputs() override
Validate Inputs code.
Definition SaveNXSPE.cpp:75
static const double MASK_ERROR
Value for error if pixel is masked.
Definition SaveNXSPE.h:73
static const char * version()
The full version number.
Energy transfer in milli-electronvolts.
Definition Unit.h:445
Time In Second.
Definition Unit.h:572
static unsigned short constexpr FLOAT64
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
std::unordered_map< UnitParams, double > UnitParametersMap
Definition Unit.h:30
std::vector< dimsize_t > DimVector
std::vector< double > MantidVec
typedef for the data storage used in Mantid matrix workspaces
Definition cow_ptr.h:172
constexpr double EMPTY_DBL() noexcept
Returns what we consider an "empty" double within a property.
Definition EmptyValues.h:42
Generate a tableworkspace to store the calibration results.
@ Undefined
this mode should not be displayed among modes availible to select but may have string representation
Definition DeltaEMode.h:33
@ Input
An input workspace.
Definition Property.h:53