Mantid
Loading...
Searching...
No Matches
SaveGDA.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
9#include "MantidAPI/Axis.h"
15#include "MantidKernel/Unit.h"
16
17#include <cmath>
18#include <fstream>
19#include <iomanip>
20#include <optional>
21#include <sstream>
22
23#include <boost/algorithm/string/classification.hpp>
24#include <boost/algorithm/string/split.hpp>
25
26namespace Mantid::DataHandling {
27
28using namespace API;
29
30namespace { // helper functions
31
32const int POINTS_PER_LINE = 4;
33
34double mean(const std::vector<double> &values) {
35 return std::accumulate(values.begin(), values.end(), 0.0) / static_cast<double>(values.size());
36}
37
38// Compute the mean resolution of the x axis of the input workspace
39// Resolution is calculated as the difference between adjacent pairs of values,
40// normalised by the second of the two
41double computeAverageDeltaTByT(const HistogramData::HistogramX &tValues) {
42 std::vector<double> deltaTByT;
43 deltaTByT.reserve(tValues.size() - 1);
44
45 std::adjacent_difference(tValues.begin(), tValues.end(), std::back_inserter(deltaTByT),
46 [](const double previous, const double current) { return (previous - current) / current; });
47 // First element is just first element of tValues, so remove it
48 deltaTByT.erase(deltaTByT.begin());
49 return mean(deltaTByT);
50}
51
52std::string generateBankHeader(int bank, int minT, size_t numberBins, double deltaTByT) {
53 std::stringstream stream;
54 const auto numberLines = static_cast<size_t>(std::ceil(static_cast<double>(numberBins) / POINTS_PER_LINE));
55
56 stream << std::setprecision(2) << "BANK " << bank << " " << numberBins << " " << numberLines << " RALF " << minT
57 << " 96 " << minT << " " << deltaTByT << " ALT";
58 return stream.str();
59}
60
61std::optional<std::vector<std::string>> getParamLinesFromGSASFile(const std::string &paramsFilename) {
62 // ICONS signifies that a line contains TOF to D conversion factors
63 const static std::string paramLineDelimiter = "ICONS";
64 std::ifstream paramsFile;
65 paramsFile.open(paramsFilename);
66
67 if (paramsFile.is_open()) {
68 std::vector<std::string> paramLines;
69 std::string line;
70 while (std::getline(paramsFile, line)) {
71 if (line.find(paramLineDelimiter) != std::string::npos) {
72 paramLines.emplace_back(line);
73 }
74 }
75 return paramLines;
76 } else {
77 return std::nullopt;
78 }
79}
80
81} // anonymous namespace
82
83DECLARE_ALGORITHM(SaveGDA)
84
85SaveGDA::CalibrationParams::CalibrationParams(const double _difc, const double _difa, const double _tzero)
86 : difa(_difa), difc(_difc), tzero(_tzero) {}
87
88const std::string SaveGDA::name() const { return "SaveGDA"; }
89
90const std::string SaveGDA::summary() const {
91 return "Save a group of focused banks to the MAUD three-column GDA format";
92}
93
94int SaveGDA::version() const { return 1; }
95
96const std::vector<std::string> SaveGDA::seeAlso() const { return {"SaveBankScatteringAngles"}; }
97
98const std::string SaveGDA::category() const { return "DataHandling\\Text;Diffraction\\DataHandling"; }
99
100const std::string SaveGDA::PROP_OUTPUT_FILENAME = "OutputFilename";
101
102const std::string SaveGDA::PROP_INPUT_WS = "InputWorkspace";
103
104const std::string SaveGDA::PROP_PARAMS_FILENAME = "GSASParamFile";
105
106const std::string SaveGDA::PROP_GROUPING_SCHEME = "GroupingScheme";
107
110 "A GroupWorkspace where every sub-workspace is a "
111 "single-spectra focused run corresponding to a particular "
112 "bank");
113
114 const static std::vector<std::string> outExts{".gda"};
115 declareProperty(std::make_unique<FileProperty>(PROP_OUTPUT_FILENAME, "", FileProperty::Save, outExts),
116 "The name of the file to save to");
117
118 const static std::vector<std::string> paramsExts{".ipf", ".prm", ".parm", ".iprm"};
119 declareProperty(std::make_unique<FileProperty>(PROP_PARAMS_FILENAME, "", FileProperty::Load, paramsExts),
120 "GSAS calibration file containing conversion factors from D to TOF");
121
123 "An array of bank IDs, where the value at element i is the "
124 "ID of the bank in " +
125 PROP_PARAMS_FILENAME + " to associate spectrum i with");
126}
127
129 const std::string filename = getProperty(PROP_OUTPUT_FILENAME);
130 std::ofstream outFile(filename.c_str());
131
132 if (!outFile) {
133 throw Kernel::Exception::FileError("Unable to create file: ", filename);
134 }
135
136 outFile << std::fixed << std::setprecision(0) << std::setfill(' ');
137
139 const auto calibParams = parseParamsFile();
140 const std::vector<int> groupingScheme = getProperty(PROP_GROUPING_SCHEME);
141
142 for (int i = 0; i < inputWS->getNumberOfEntries(); ++i) {
143 const auto ws = inputWS->getItem(i);
144 const auto matrixWS = std::dynamic_pointer_cast<MatrixWorkspace>(ws);
145
146 auto x = matrixWS->dataX(0);
147 const size_t bankIndex(groupingScheme[i] - 1);
148 if (bankIndex >= calibParams.size()) {
149 throw Kernel::Exception::IndexError(bankIndex, calibParams.size(), "Bank number out of range");
150 }
151 const auto &bankCalibParams = calibParams[bankIndex];
152
153 // For historic reasons, TOF is scaled by 32 in MAUD
154 const static double tofScale = 32;
155 std::vector<double> tofScaled;
156 tofScaled.reserve(x.size());
157 Kernel::Units::dSpacing dSpacingUnit;
158 std::vector<double> yunused;
159 dSpacingUnit.toTOF(x, yunused, 0., Kernel::DeltaEMode::Elastic,
160 {{Kernel::UnitParams::difa, bankCalibParams.difa},
161 {Kernel::UnitParams::difc, bankCalibParams.difc},
162 {Kernel::UnitParams::tzero, bankCalibParams.tzero}});
163 std::transform(x.begin(), x.end(), std::back_inserter(tofScaled),
164 [](const double tofVal) { return tofVal * tofScale; });
165 const auto averageDeltaTByT = computeAverageDeltaTByT(tofScaled);
166
167 const auto &intensity = matrixWS->y(0);
168 const auto &error = matrixWS->e(0);
169 const auto numPoints = std::min({tofScaled.size(), intensity.size(), error.size()});
170
171 const auto header =
172 generateBankHeader(i + 1, static_cast<int>(std::round(tofScaled[0])), numPoints, averageDeltaTByT);
173
174 outFile << std::left << std::setw(80) << header << '\n' << std::right;
175
176 for (size_t j = 0; j < numPoints; ++j) {
177 outFile << std::setw(8) << tofScaled[j] << std::setw(7) << intensity[j] * 1000 << std::setw(5) << error[j] * 1000;
178
179 if (j % POINTS_PER_LINE == POINTS_PER_LINE - 1) {
180 // new line every 4 points
181 outFile << '\n';
182 } else if (j == numPoints - 1) {
183 // make sure line is 80 characters long
184 outFile << std::string(80 - (i % POINTS_PER_LINE + 1) * 20, ' ') << '\n';
185 }
186 }
187 }
188}
189
190std::map<std::string, std::string> SaveGDA::validateInputs() {
191 std::map<std::string, std::string> issues;
192 std::optional<std::string> inputWSIssue;
193
195 for (const auto &ws : *inputWS) {
196 const auto matrixWS = std::dynamic_pointer_cast<MatrixWorkspace>(ws);
197 if (matrixWS) {
198 if (matrixWS->getNumberHistograms() != 1) {
199 inputWSIssue = "The workspace " + matrixWS->getName() +
200 " has the wrong number of histograms. It "
201 "should contain data for a single focused "
202 "spectra";
203 } else if (matrixWS->getAxis(0)->unit()->unitID() != "dSpacing") {
204 inputWSIssue = "The workspace " + matrixWS->getName() +
205 " has incorrect units. SaveGDA "
206 "expects input workspaces with "
207 "units of D-spacing";
208 }
209 } else { // not matrixWS
210 inputWSIssue = "The workspace " + ws->getName() + " is of the wrong type. It should be a MatrixWorkspace";
211 }
212 }
213 if (inputWSIssue) {
214 issues[PROP_INPUT_WS] = *inputWSIssue;
215 }
216
217 const std::vector<int> groupingScheme = getProperty(PROP_GROUPING_SCHEME);
218 const auto numSpectraInGroupingScheme = groupingScheme.size();
219 const auto numSpectraInWS = static_cast<size_t>(inputWS->getNumberOfEntries());
220 if (numSpectraInGroupingScheme != numSpectraInWS) {
221 issues[PROP_GROUPING_SCHEME] = "The grouping scheme must contain one entry for every focused spectrum "
222 "in the input workspace. " +
223 PROP_GROUPING_SCHEME + " has " + std::to_string(numSpectraInGroupingScheme) +
224 " entries whereas " + PROP_INPUT_WS + " has " + std::to_string(numSpectraInWS);
225 }
226
227 return issues;
228}
229
230std::vector<SaveGDA::CalibrationParams> SaveGDA::parseParamsFile() const {
231 const std::string paramsFilename = getProperty(PROP_PARAMS_FILENAME);
232 const auto paramLines = getParamLinesFromGSASFile(paramsFilename);
233 if (!paramLines) {
234 g_log.error(strerror(errno));
235 throw Kernel::Exception::FileError("Could not read GSAS parameter file", paramsFilename);
236 }
237 std::vector<CalibrationParams> calibParams;
238 for (const auto &paramLine : *paramLines) {
239 std::vector<std::string> lineItems;
240 boost::algorithm::split(lineItems, paramLine, boost::is_any_of("\t "), boost::token_compress_on);
241 calibParams.emplace_back(std::stod(lineItems[3]), std::stod(lineItems[4]), std::stod(lineItems[5]));
242 }
243 return calibParams;
244}
245
246} // namespace Mantid::DataHandling
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:538
double error
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Kernel::Logger & g_log
Definition Algorithm.h:422
@ Save
to specify a file to write to, the file may or may not exist
@ Load
allowed here which will be passed to the algorithm
A property class for workspaces.
const std::string name() const override
function to return a name of the algorithm, must be overridden in all algorithms
Definition SaveGDA.cpp:88
int version() const override
function to return a version of the algorithm, must be overridden in all algorithms
Definition SaveGDA.cpp:94
static const std::string PROP_GROUPING_SCHEME
Definition SaveGDA.h:43
static const std::string PROP_PARAMS_FILENAME
Definition SaveGDA.h:42
const std::string category() const override
function to return a category of the algorithm.
Definition SaveGDA.cpp:98
std::map< std::string, std::string > validateInputs() override
Perform validation of ALL the input properties of the algorithm.
Definition SaveGDA.cpp:190
std::vector< CalibrationParams > parseParamsFile() const
Definition SaveGDA.cpp:230
const std::string summary() const override
function returns a summary message that will be displayed in the default GUI, and in the help.
Definition SaveGDA.cpp:90
static const std::string PROP_INPUT_WS
Definition SaveGDA.h:41
void exec() override
Virtual method - must be overridden by concrete algorithm.
Definition SaveGDA.cpp:128
const std::vector< std::string > seeAlso() const override
Function to return all of the seeAlso (these are not validated) algorithms related to this algorithm....
Definition SaveGDA.cpp:96
static const std::string PROP_OUTPUT_FILENAME
Definition SaveGDA.h:40
void init() override
Virtual method - must be overridden by concrete algorithm.
Definition SaveGDA.cpp:108
Support for a property that holds an array of values.
Records the filename and the description of failure.
Definition Exception.h:98
Exception for index errors.
Definition Exception.h:284
void error(const std::string &msg)
Logs at error level.
Definition Logger.cpp:108
void toTOF(std::vector< double > &xdata, std::vector< double > const &ydata, const double &_l1, const int &_emode, std::initializer_list< std::pair< const UnitParams, double > > params)
Convert from the concrete unit to time-of-flight.
Definition Unit.cpp:148
d-Spacing in Angstrom
Definition Unit.h:351
std::shared_ptr< WorkspaceGroup > WorkspaceGroup_sptr
shared pointer to Mantid::API::WorkspaceGroup
std::string to_string(const wide_integer< Bits, Signed > &n)
@ Input
An input workspace.
Definition Property.h:53