Mantid
Loading...
Searching...
No Matches
SaveSESANS.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 +
9
12#include "MantidAPI/Run.h"
13#include "MantidAPI/Sample.h"
27#include "MantidTypes/Core/DateAndTime.h"
28
29#include <algorithm>
30#include <cmath>
31#include <fstream>
32#include <iomanip>
33#include <map>
34#include <optional>
35#include <set>
36#include <string>
37
38namespace Mantid::DataHandling {
39
40namespace {
47std::string sanitiseHeaderValue(const std::string &value) {
49}
50
59template <typename T>
60std::optional<std::string> distinctValuesInTimeROI(const Kernel::Property *log, const Kernel::TimeROI &roi) {
61 const auto *timeSeries = dynamic_cast<const Kernel::TimeSeriesProperty<T> *>(log);
62 if (!timeSeries) {
63 return std::nullopt;
64 }
65 if (timeSeries->size() == 0) {
66 return std::string();
67 }
68 std::vector<std::string> values;
69 for (const auto &rawValue : timeSeries->filteredValuesAsVector(&roi)) {
70 const auto value = sanitiseHeaderValue(Kernel::Strings::toString(rawValue));
71 if (!value.empty() && (values.empty() || values.back() != value)) {
72 values.emplace_back(value);
73 }
74 }
75 return Kernel::Strings::join(values.cbegin(), values.cend(), ", ");
76}
77} // namespace
78
79// Register the algorithm with the AlgorithmFactory
80DECLARE_ALGORITHM(SaveSESANS)
81
82
83const std::string SaveSESANS::name() const { return "SaveSESANS"; }
84
86const std::string SaveSESANS::summary() const { return "Save a file using the SESANS format"; }
87
89int SaveSESANS::version() const { return 1; }
90
92const std::string SaveSESANS::category() const { return "DataHandling\\Text"; }
93
94std::map<std::string, std::string> SaveSESANS::validateInputs() {
95 std::map<std::string, std::string> invalidInputs;
96
97 for (const auto &propertyName : mandatoryDoubleProperties) {
98 double value = getProperty(propertyName);
99 if (value == EMPTY_DBL()) {
100 invalidInputs[propertyName] = propertyName + " must be set";
101 }
102 }
103
104 if (getPropertyValue("Sample").empty()) {
105 invalidInputs["Sample"] = "Sample must be set";
106 }
107
108 m_sampleThickness = static_cast<double>(getProperty("OverrideSampleThickness"));
110 invalidInputs["OverrideSampleThickness"] = "OverrideSampleThickness value must be greater than 0";
111 }
112
113 return invalidInputs;
114}
115
120 auto validOrientation = std::make_shared<Kernel::StringListValidator>(std::set<std::string>{"X", "Y", "Z"});
121
122 declareProperty(std::make_unique<API::WorkspaceProperty<>>("InputWorkspace", "", Kernel::Direction::Input),
123 "The name of the workspace to save");
124 declareProperty(std::make_unique<API::FileProperty>("Filename", "", API::FileProperty::Save, fileExtensions),
125 "The name to use when saving the file");
126
127 declareProperty("ThetaZMax", EMPTY_DBL(), "The angular acceptance in the encoding direction",
129 declareProperty("ThetaZMaxUnit", "radians", Kernel::Direction::Input);
130 declareProperty("ThetaYMax", EMPTY_DBL(), "The angular acceptance in the non-encoding direction",
132 declareProperty("ThetaYMaxUnit", "radians", Kernel::Direction::Input);
133 declareProperty("EchoConstant", EMPTY_DBL(), "The spin echo length, in nanometers, probed by a 1A neutron",
135
136 declareProperty<std::string>("Sample", "", "Sample name", Kernel::Direction::Input);
137
138 declareProperty<std::string>("Orientation", "Z", validOrientation, "Orientation of the instrument");
139
140 declareProperty("OverrideSampleThickness", EMPTY_DBL(),
141 "The sample thickness in mm. If set, this value will be used instead of the thickness from the input "
142 "workspace sample",
144
145 declareProperty<std::string>("Facility", "",
146 "The facility where the data was measured. If not set, the facility is determined "
147 "from the instrument",
149 declareProperty<std::string>("Instrument", "",
150 "The instrument used to measure the data. If not set, the instrument name is taken "
151 "from the input workspace",
153 declareProperty<std::string>("RunNumber", "",
154 "The run number of the sample measurement. If not set, the run_number log of the "
155 "input workspace is used",
157 declareProperty<std::string>("SamplePosition", "",
158 "The name of the sample changer position, e.g. AT or 1CT. If not set, the positions "
159 "recorded in the SamplePos log of the input workspace during the selected time range "
160 "are used",
162 declareProperty<std::string>("BlankRunNumber", "", "The run number of the blank measurement. Optional.",
164 declareProperty<std::string>("SampleTransmissionRunNumber", "",
165 "The run number of the sample transmission measurement. Optional.",
167 declareProperty<std::string>("BlankTransmissionRunNumber", "",
168 "The run number of the blank transmission measurement. Optional.",
170}
171
179 const std::string instrument = getPropertyValue("Instrument");
180 if (!instrument.empty()) {
181 return instrument;
182 }
183 const auto wsInstrument = ws->getInstrument();
184 return wsInstrument ? wsInstrument->getName() : "";
185}
186
193std::string SaveSESANS::getFacilityName(const std::string &instrumentName) const {
194 const std::string facility = getPropertyValue("Facility");
195 if (!facility.empty() || instrumentName.empty()) {
196 return facility;
197 }
198 try {
199 return Kernel::ConfigService::Instance().getInstrument(instrumentName).facility().name();
200 } catch (const Kernel::Exception::NotFoundError &) {
201 g_log.information("Could not determine the facility for instrument " + instrumentName +
202 ". Use the Facility property to provide it.");
203 return "";
204 }
205}
206
214 const std::string runNumber = getPropertyValue("RunNumber");
215 if (!runNumber.empty()) {
216 return runNumber;
217 }
218 const auto &run = ws->run();
219 return run.hasProperty("run_number") ? run.getLogData("run_number")->value() : "";
220}
221
232 const auto &roi = run.getTimeROI();
233 if (!roi.useAll()) {
234 return roi;
235 }
236 try {
237 const auto startTime = run.startTime();
238 const auto endTime = run.endTime();
239 if (startTime < endTime) {
240 return Kernel::TimeROI(startTime, endTime);
241 }
242 } catch (const std::runtime_error &) {
243 g_log.debug("The run has no valid start and end times, so the whole SamplePos log is used");
244 }
245 return roi;
246}
247
257 const std::string samplePosition = getPropertyValue("SamplePosition");
258 if (!samplePosition.empty()) {
259 return samplePosition;
260 }
261 const auto &run = ws->run();
262 if (!run.hasProperty("SamplePos")) {
263 return "";
264 }
265 const auto *log = run.getLogData("SamplePos");
266 const auto roi = getSamplePositionTimeROI(run);
267 // IBEX records the position as a string or a number, depending on how the block is configured
268 auto positions = distinctValuesInTimeROI<std::string>(log, roi);
269 if (!positions) {
270 positions = distinctValuesInTimeROI<double>(log, roi);
271 }
272 if (!positions) {
273 positions = distinctValuesInTimeROI<float>(log, roi);
274 }
275 if (!positions) {
276 positions = distinctValuesInTimeROI<int>(log, roi);
277 }
278 if (positions) {
279 return *positions;
280 }
281 if (dynamic_cast<const Kernel::ITimeSeriesProperty *>(log)) {
282 g_log.warning("The SamplePos log has an unsupported type and is not written to the file. Use the SamplePosition "
283 "property to provide the sample position.");
284 return "";
285 }
286 return log->value();
287}
288
293 API::MatrixWorkspace_const_sptr ws = getProperty("InputWorkspace");
294
295 // Check workspace has only one spectrum
296 if (ws->getNumberHistograms() != 1) {
297 g_log.error("This algorithm expects a workspace with exactly 1 spectrum");
298 throw std::runtime_error("SaveSESANS passed workspace with incorrect "
299 "number of spectra, expected 1");
300 }
301
302 if (m_sampleThickness == EMPTY_DBL()) {
303 m_sampleThickness = ws->sample().getThickness();
305 g_log.error("The workspace passed in does not provide the sample thickness. Please use the "
306 "OverrideSampleThickness property to "
307 "provide this value instead");
308 throw std::runtime_error("SaveSESANS passed workspace with sample thickness less than "
309 "or equal to 0 and no value provided for OverrideSampleThickness property");
310 }
311 }
312
313 auto filename = getPropertyValue("Filename");
314 std::ofstream outfile(filename, std::ofstream::trunc);
315 if (outfile.fail()) {
316 const std::string error = strerror(errno);
317 g_log.error("Failed to open file. Error was: " + error);
318 throw std::runtime_error("Could not open file at the following path: " + filename);
319 }
320
321 writeHeaders(outfile, ws);
322 outfile << "\n"
323 << "BEGIN_DATA"
324 << "\n";
325
326 const auto &wavelength = ws->points(0);
327 const auto &yValues = ws->y(0);
328 const auto &eValues = ws->e(0);
329
330 const auto spinEchoLength = calculateSpinEchoLength(wavelength);
331 const auto depolarisation = calculateDepolarisation(yValues, wavelength);
332 const auto error = calculateError(eValues, yValues, wavelength);
333
334 outfile << "SpinEchoLength Depolarisation Depolarisation_error Wavelength\n";
335
336 for (size_t i = 0; i < spinEchoLength.size(); ++i) {
337 outfile << formatDouble(spinEchoLength[i]) << " ";
338 outfile << formatDouble(depolarisation[i]) << " ";
339 outfile << formatDouble(error[i]) << " ";
340 outfile << formatDouble(wavelength[i]) << "\n";
341 }
342
343 outfile.close();
344}
345
352 writeHeader(outfile, "FileFormatVersion", "1.0");
353 writeHeader(outfile, "DataFileTitle", ws->getTitle());
354 writeHeader(outfile, "Sample", getPropertyValue("Sample"));
355 writeHeader(outfile, "Thickness", std::to_string(m_sampleThickness));
356 writeHeader(outfile, "Thickness_unit", "mm");
357 writeHeader(outfile, "Theta_zmax", getPropertyValue("ThetaZMax"));
358 writeHeader(outfile, "Theta_zmax_unit", getPropertyValue("ThetaZMaxUnit"));
359 writeHeader(outfile, "Theta_ymax", getPropertyValue("ThetaYMax"));
360 writeHeader(outfile, "Theta_ymax_unit", getPropertyValue("ThetaYMaxUnit"));
361 writeHeader(outfile, "Orientation", getPropertyValue("Orientation"));
362 writeHeader(outfile, "SpinEchoLength_unit", "A");
363 writeHeader(outfile, "Depolarisation_unit", "A-2 cm-1");
364 writeHeader(outfile, "Wavelength_unit", "A");
365 writeHeader(outfile, "Echo_constant", getPropertyValue("EchoConstant"));
366
367 // Optional metadata identifying the measurement. These are only written if
368 // a value is available
369 const auto instrumentName = getInstrumentName(ws);
370 writeOptionalHeader(outfile, "Facility", getFacilityName(instrumentName));
371 writeOptionalHeader(outfile, "Instrument", instrumentName);
372 writeOptionalHeader(outfile, "Run_number", getRunNumber(ws));
373 writeOptionalHeader(outfile, "Sample_position", getSamplePosition(ws));
374 writeOptionalHeader(outfile, "Blank_run_number", getPropertyValue("BlankRunNumber"));
375 writeOptionalHeader(outfile, "Sample_transmission_run_number", getPropertyValue("SampleTransmissionRunNumber"));
376 writeOptionalHeader(outfile, "Blank_transmission_run_number", getPropertyValue("BlankTransmissionRunNumber"));
377
378 writeHeader(outfile, "Reduction_software", "Mantid");
379 writeHeader(outfile, "Software_version", Kernel::MantidVersion::version());
380 writeHeader(outfile, "Reduction_date", Types::Core::DateAndTime::getCurrentTime().toISO8601String());
381}
382
389void SaveSESANS::writeHeader(std::ofstream &outfile, const std::string &name, const std::string &value) {
390 // Always separate the name from the value, even if the name is longer than the usual column width
391 outfile << std::setfill(' ') << std::setw(MAX_HDR_LENGTH) << std::left << name;
392 if (name.size() >= static_cast<size_t>(MAX_HDR_LENGTH)) {
393 outfile << " ";
394 }
395 outfile << value << "\n";
396}
397
405void SaveSESANS::writeOptionalHeader(std::ofstream &outfile, const std::string &name, const std::string &value) {
406 const auto sanitisedValue = sanitiseHeaderValue(value);
407 if (!sanitisedValue.empty()) {
408 writeHeader(outfile, name, sanitisedValue);
409 }
410}
411
418std::vector<double> SaveSESANS::calculateSpinEchoLength(const HistogramData::Points &wavelength) const {
419 std::vector<double> spinEchoLength;
420 const double echoConstant = getProperty("EchoConstant");
421
422 // SEL is calculated as wavelength^2 * echoConstant
423 transform(wavelength.begin(), wavelength.end(), back_inserter(spinEchoLength),
424 [&](double w) { return w * w * echoConstant; });
425 return spinEchoLength;
426}
427
436std::vector<double> SaveSESANS::calculateDepolarisation(const HistogramData::HistogramY &yValues,
437 const HistogramData::Points &wavelength) const {
438 Mantid::MantidVec depolarisation;
439 // The sample thickness is provided in mm but we need to use cm here
440 double thickness = m_sampleThickness * 0.1;
441
442 // Depol is calculated as ln(y) / wavelength^2 / sample thickness in cm
443 transform(yValues.begin(), yValues.end(), wavelength.begin(), back_inserter(depolarisation),
444 [&](double y, double w) { return log(y) / (w * w) / thickness; });
445 return depolarisation;
446}
447
456Mantid::MantidVec SaveSESANS::calculateError(const HistogramData::HistogramE &eValues,
457 const HistogramData::HistogramY &yValues,
458 const HistogramData::Points &wavelength) const {
460
461 // The sample thickness is provided in mm but we need to use cm here
462 double thickness = m_sampleThickness * 0.1;
463
464 // Error is calculated as e / (y * wavelength^2) / sample thickness in cm
465 for (size_t i = 0; i < eValues.size(); i++) {
466 error.emplace_back(eValues[i] / (yValues[i] * wavelength[i] * wavelength[i]) / thickness);
467 }
468 return error;
469}
470
471} // namespace Mantid::DataHandling
std::string name
Definition Run.cpp:60
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:547
double value
The value of the point.
Definition FitMW.cpp:51
double error
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.
Kernel::Logger & g_log
Definition Algorithm.h:426
@ Save
to specify a file to write to, the file may or may not exist
const Types::Core::DateAndTime endTime() const
Return the run end time.
const Kernel::TimeROI & getTimeROI() const
const Types::Core::DateAndTime startTime() const
Return the run start time.
This class stores information regarding an experimental run as a series of log entries.
Definition Run.h:36
A property class for workspaces.
SaveSESANS : Save a workspace in the SESANS file format.
Definition SaveSESANS.h:52
const std::string category() const override
Get the algorithm's category.
std::vector< double > calculateSpinEchoLength(const HistogramData::Points &wavelength) const
Calculate SpinEchoLength column from wavelength ( SEL = wavelength * wavelength * echoConstant)
Mantid::MantidVec calculateError(const HistogramData::HistogramE &eValues, const HistogramData::HistogramY &yValues, const HistogramData::Points &wavelength) const
Calculate the error column from the workspace values (error = e / (y * wavelength^2)) / sample thickn...
std::vector< double > calculateDepolarisation(const HistogramData::HistogramY &yValues, const HistogramData::Points &wavelength) const
Calculate the depolarisation column from wavelength and Y values in the workspace (depol = ln(y) / wa...
int version() const override
Get the version number of the algorithm.
std::string getInstrumentName(const API::MatrixWorkspace_const_sptr &ws) const
Get the instrument name, either from the Instrument property or from the input workspace.
void init() override
Initialise the algorithm.
const std::vector< std::string > fileExtensions
Definition SaveSESANS.h:67
std::string getFacilityName(const std::string &instrumentName) const
Get the facility name, either from the Facility property or by looking up the instrument in the facil...
std::map< std::string, std::string > validateInputs() override
Perform validation of ALL the input properties of the algorithm.
const std::string name() const override
Get the algorithm's name.
void writeHeader(std::ofstream &outfile, const std::string &name, const std::string &value)
Write a single header to the output file.
void writeHeaders(std::ofstream &outfile, API::MatrixWorkspace_const_sptr &ws)
Write header values to the output file.
Kernel::TimeROI getSamplePositionTimeROI(const API::Run &run) const
Get the time range to read the sample position for.
void exec() override
Execute the algorithm.
std::string getRunNumber(const API::MatrixWorkspace_const_sptr &ws) const
Get the run number, either from the RunNumber property or from the run_number log of the input worksp...
void writeOptionalHeader(std::ofstream &outfile, const std::string &name, const std::string &value)
Write a single header to the output file, if it has a value.
std::string getSamplePosition(const API::MatrixWorkspace_const_sptr &ws) const
Get the sample position, either from the SamplePosition property or from the SamplePos log of the inp...
const std::vector< std::string > mandatoryDoubleProperties
Definition SaveSESANS.h:68
const std::string summary() const override
Get a summary of the algorithm.
Exception for when an item is not found in a collection.
Definition Exception.h:145
A non-templated interface to a TimeSeriesProperty.
void debug(const std::string &msg)
Logs at debug level.
Definition Logger.cpp:145
void error(const std::string &msg)
Logs at error level.
Definition Logger.cpp:108
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
void information(const std::string &msg)
Logs at information level.
Definition Logger.cpp:136
static const std::string & version()
The full version number.
TimeROI : Object that holds information about when the time measurement was active.
Definition TimeROI.h:18
std::shared_ptr< const MatrixWorkspace > MatrixWorkspace_const_sptr
shared pointer to the matrix workspace base class (const version)
MANTID_DATAHANDLING_DLL std::string formatDouble(double const value)
MANTID_KERNEL_DLL std::string strip(const std::string &A)
strip pre/post spaces
Definition Strings.cpp:420
MANTID_KERNEL_DLL std::string replaceAll(std::string const &input, char const to_replace, char const substitute)
Return a string with all occurrences of indicated character replaced by the new character.
Definition Strings.cpp:91
std::string join(ITERATOR_TYPE begin, ITERATOR_TYPE end, const std::string &separator, typename std::enable_if<!(std::is_same< typename std::iterator_traits< ITERATOR_TYPE >::iterator_category, std::random_access_iterator_tag >::value)>::type *=nullptr)
Join a set or vector of (something that turns into a string) together into one string,...
Definition Strings.h:84
std::string toString(const T &value)
Convert a number to a string.
Definition Strings.cpp:735
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
STL namespace.
std::string to_string(const wide_integer< Bits, Signed > &n)
@ Input
An input workspace.
Definition Property.h:54