Mantid
Loading...
Searching...
No Matches
FindReflectometryLines3.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2026 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
20
21#include <algorithm>
22#include <cmath>
23#include <iterator>
24#include <optional>
25#include <stdexcept>
26#include <utility>
27#include <vector>
28
29namespace {
30namespace Prop {
31std::string const ACCEPT_CHANGES_IN_FUNCTION{"AcceptChangesInFunctionTooSmall"};
32std::string const ACCEPT_CHANGES_IN_PARAMETERS{"AcceptChangesInParameterTooSmall"};
33std::string const BACKGROUND_TYPE{"BackgroundType"};
34std::string const END_INDEX{"EndWorkspaceIndex"};
35std::string const FIT_WINDOW_MULTIPLIER{"FitWindowMultiplier"};
36std::string const INPUT_WS{"InputWorkspace"};
37std::string const OUTPUT_FIT_WS{"OutputFitWorkspace"};
38std::string const OUTPUT_PROFILE_WS{"OutputProfileWorkspace"};
39std::string const OUTPUT_STATUS{"OutputStatus"};
40std::string const LINE_CENTRE{"LineCentre"};
41std::string const RANGE_LOWER{"RangeLower"};
42std::string const RANGE_UPPER{"RangeUpper"};
43std::string const START_INDEX{"StartWorkspaceIndex"};
44std::string const USE_FITTED_CENTRE_ON_FAILURE{"UseFittedLineCentreOnFailure"};
45} // namespace Prop
46
47std::string const LINEAR_BACKGROUND{"Linear"};
48std::string const FLAT_BACKGROUND{"Flat"};
49std::string const FALLBACK_STATUS{"Fit failed; using initial line centre"};
50
51// Give every integrated spectrum common bin edges so that Transpose accepts the workspace. These edges become the
52// unused vertical axis of the detector profile and do not affect the fitted workspace-index coordinates.
53void setCommonBinEdgesForTranspose(Mantid::API::MatrixWorkspace &workspace) {
54 for (size_t index = 0; index < workspace.getNumberHistograms(); ++index) {
55 auto &x = workspace.mutableX(index);
56 x.front() = 0.0;
57 x.back() = 1.0;
58 }
59}
60
61double median(const Mantid::HistogramData::HistogramY &y) {
62 auto finiteValues = std::vector<double>{};
63 finiteValues.reserve(y.size());
64 std::copy_if(y.cbegin(), y.cend(), std::back_inserter(finiteValues),
65 [](double const value) { return std::isfinite(value); });
66 if (finiteValues.empty()) {
67 throw std::runtime_error("FindReflectometryLines could not identify an initial line centre.");
68 }
70}
71
72struct PeakParameters {
73 double centre;
74 double height;
75 std::optional<double> fwhm;
76};
77
78PeakParameters estimatePeak(const Mantid::API::MatrixWorkspace &profile, double const background) {
79 auto const &x = profile.x(0);
80 auto const &y = profile.y(0);
81 auto maxIndex = std::optional<size_t>{};
82 for (size_t index = 0; index < y.size(); ++index) {
83 if (std::isfinite(y[index]) && (!maxIndex || y[index] > y[*maxIndex])) {
84 maxIndex = index;
85 }
86 }
87 if (!maxIndex) {
88 throw std::runtime_error("FindReflectometryLines could not identify an initial line centre.");
89 }
90
91 auto const height = y[*maxIndex] - background;
92 if (height <= 0.0) {
93 return {x[*maxIndex], 0.0, std::nullopt};
94 }
95
96 auto const halfHeight = background + 0.5 * height;
97 auto left = std::optional<size_t>{};
98 for (size_t index = *maxIndex; index > 0; --index) {
99 if (std::isfinite(y[index - 1]) && y[index - 1] < halfHeight) {
100 left = index - 1;
101 break;
102 }
103 }
104 auto right = std::optional<size_t>{};
105 for (size_t index = *maxIndex + 1; index < y.size(); ++index) {
106 if (std::isfinite(y[index]) && y[index] < halfHeight) {
107 right = index;
108 break;
109 }
110 }
111
112 auto fwhm = std::optional<double>{};
113 if (left && right) {
114 auto const width = x[*right] - x[*left];
115 if (width > 0.0) {
116 fwhm = width;
117 }
118 }
119 return {x[*maxIndex], height, fwhm};
120}
121
122} // namespace
123
124namespace Mantid::Algorithms {
125
126DECLARE_ALGORITHM(FindReflectometryLines3)
127
128const std::string FindReflectometryLines3::name() const { return "FindReflectometryLines"; }
129
130int FindReflectometryLines3::version() const { return 3; }
131
132const std::string FindReflectometryLines3::category() const { return "Reflectometry;ILL\\Reflectometry"; }
133
134const std::string FindReflectometryLines3::summary() const {
135 return "Finds the fractional workspace index corresponding to a reflected or direct line by fitting a Gaussian "
136 "and background to the integrated detector profile.";
137}
138
139const std::vector<std::string> FindReflectometryLines3::seeAlso() const { return {"FindPeaks"}; }
140
141bool FindReflectometryLines3::fitStatusIsAccepted(const std::string &fitStatus, const bool acceptChangesInFunction,
142 const bool acceptChangesInParameters) {
143 std::vector<std::string> acceptedStatuses{API::MinimizerStatus::SUCCESS};
144 if (acceptChangesInFunction) {
145 acceptedStatuses.emplace_back(API::MinimizerStatus::CHANGES_IN_FUNCTION_TOO_SMALL);
146 }
147 if (acceptChangesInParameters) {
148 acceptedStatuses.emplace_back(API::MinimizerStatus::CHANGES_IN_PARAMETER_TOO_SMALL);
149 }
150 return std::find(acceptedStatuses.cbegin(), acceptedStatuses.cend(), fitStatus) != acceptedStatuses.cend();
151}
152
155 std::make_unique<API::WorkspaceProperty<API::MatrixWorkspace>>(Prop::INPUT_WS, "", Kernel::Direction::Input),
156 "A reflectometry workspace containing detector spectra.");
157
158 auto nonNegative = std::make_shared<Kernel::BoundedValidator<int>>();
159 nonNegative->setLower(0);
160 declareProperty(Prop::START_INDEX, 0, nonNegative,
161 "Workspace index of the first spectrum to include in the detector profile.");
162 declareProperty(Prop::END_INDEX, EMPTY_INT(), nonNegative,
163 "Workspace index of the last spectrum to include in the detector profile.");
164 declareProperty(Prop::RANGE_LOWER, EMPTY_DBL(), "Lower X limit used when integrating each spectrum.");
165 declareProperty(Prop::RANGE_UPPER, EMPTY_DBL(), "Upper X limit used when integrating each spectrum.");
166
167 auto positive = std::make_shared<Kernel::BoundedValidator<double>>();
168 positive->setLower(0.0);
169 positive->setLowerExclusive(true);
170 declareProperty(Prop::FIT_WINDOW_MULTIPLIER, 3.0, positive,
171 "Number of estimated peak FWHMs included on either side of the initial line centre.");
172
173 auto const backgrounds = std::vector<std::string>{LINEAR_BACKGROUND, FLAT_BACKGROUND};
174 declareProperty(Prop::BACKGROUND_TYPE, LINEAR_BACKGROUND, std::make_shared<Kernel::StringListValidator>(backgrounds),
175 "Background function fitted with the Gaussian. Choose Linear or Flat.");
176 declareProperty(Prop::ACCEPT_CHANGES_IN_FUNCTION, true,
177 "If true, accept a fit that stopped because changes in the function value became too small.");
178 declareProperty(Prop::ACCEPT_CHANGES_IN_PARAMETERS, true,
179 "If true, accept a fit that stopped because changes in the parameter values became too small.");
180 declareProperty(Prop::USE_FITTED_CENTRE_ON_FAILURE, false,
181 "If true, use a finite fitted peak centre when Fit completes with an unsuccessful status. If false, "
182 "use the initial line centre.");
183
185 Prop::OUTPUT_PROFILE_WS, "", Kernel::Direction::Output, API::PropertyMode::Optional),
186 "The integrated detector profile used for peak fitting, with X values corresponding to input "
187 "workspace indices.");
190 "The Fit output containing the data, fitted curve, and residuals. Not set when the initial peak "
191 "centre is returned.");
192 declareProperty(Prop::LINE_CENTRE, EMPTY_DBL(), "The fractional workspace index of the specular line centre.",
194 declareProperty(Prop::OUTPUT_STATUS, std::string{},
195 "The Fit status when a fitted line centre is returned, otherwise reports that the initial line "
196 "centre was used.",
198}
199
200std::map<std::string, std::string> FindReflectometryLines3::validateInputs() {
201 std::map<std::string, std::string> issues;
203 // Direct validation can receive a WorkspaceGroup before the framework dispatches its members.
204 if (!workspace) {
205 return issues;
206 }
207
208 int const startIndexProperty = getProperty(Prop::START_INDEX);
209 auto const startIndex = static_cast<size_t>(startIndexProperty);
210 if (startIndex >= workspace->getNumberHistograms()) {
211 issues[Prop::START_INDEX] = "The index must be smaller than the number of spectra in the input workspace.";
212 }
213 if (!isDefault(Prop::END_INDEX)) {
214 int const endIndexProperty = getProperty(Prop::END_INDEX);
215 auto const endIndex = static_cast<size_t>(endIndexProperty);
216 if (endIndex >= workspace->getNumberHistograms()) {
217 issues[Prop::END_INDEX] = "The index must be smaller than the number of spectra in the input workspace.";
218 } else if (startIndex > endIndex) {
219 issues[Prop::END_INDEX] = "The index must not be smaller than StartWorkspaceIndex.";
220 }
221 }
222 if (!isDefault(Prop::RANGE_LOWER) && !isDefault(Prop::RANGE_UPPER)) {
223 double const lower = getProperty(Prop::RANGE_LOWER);
224 double const upper = getProperty(Prop::RANGE_UPPER);
225 if (lower >= upper) {
226 issues[Prop::RANGE_UPPER] = "RangeUpper must be greater than RangeLower.";
227 }
228 }
229 return issues;
230}
231
233 auto integration = createChildAlgorithm("Integration");
234 integration->setAlwaysStoreInADS(false);
235 integration->setProperty("InputWorkspace", inputWorkspace);
236 int const startIndexProperty = getProperty(Prop::START_INDEX);
237 integration->setProperty("StartWorkspaceIndex", startIndexProperty);
238 if (!isDefault(Prop::END_INDEX)) {
239 int const endIndexProperty = getProperty(Prop::END_INDEX);
240 integration->setProperty("EndWorkspaceIndex", endIndexProperty);
241 }
242 if (!isDefault(Prop::RANGE_LOWER)) {
243 integration->setProperty("RangeLower", static_cast<double>(getProperty(Prop::RANGE_LOWER)));
244 }
245 if (!isDefault(Prop::RANGE_UPPER)) {
246 integration->setProperty("RangeUpper", static_cast<double>(getProperty(Prop::RANGE_UPPER)));
247 }
248 integration->execute();
249 API::MatrixWorkspace_sptr integratedWorkspace = integration->getProperty("OutputWorkspace");
250 setCommonBinEdgesForTranspose(*integratedWorkspace);
251
252 auto transpose = createChildAlgorithm("Transpose");
253 transpose->setAlwaysStoreInADS(false);
254 transpose->setProperty("InputWorkspace", integratedWorkspace);
255 transpose->execute();
256 API::MatrixWorkspace_sptr profileWorkspace = transpose->getProperty("OutputWorkspace");
257
258 auto &x = profileWorkspace->mutableX(0);
259 auto const firstWorkspaceIndex = static_cast<double>(startIndexProperty);
260 for (size_t index = 0; index < x.size(); ++index) {
261 x[index] = firstWorkspaceIndex + static_cast<double>(index);
262 }
263 return profileWorkspace;
264}
265
267 API::MatrixWorkspace_sptr inputWorkspace = getProperty(Prop::INPUT_WS);
268 auto profileWorkspace = createProfile(inputWorkspace);
269 if (!isDefault(Prop::OUTPUT_PROFILE_WS)) {
270 setProperty(Prop::OUTPUT_PROFILE_WS, profileWorkspace);
271 }
272
273 auto const backgroundType = getPropertyValue(Prop::BACKGROUND_TYPE);
274 auto const backgroundLevel = median(profileWorkspace->y(0));
275 auto const initialPeak = estimatePeak(*profileWorkspace, backgroundLevel);
276 if (!initialPeak.fwhm) {
277 g_log.warning() << "Could not estimate the specular peak width. Using the initial line centre.\n";
278 setProperty(Prop::LINE_CENTRE, initialPeak.centre);
279 setProperty(Prop::OUTPUT_STATUS, FALLBACK_STATUS);
280 return;
281 }
282
283 auto function = API::FunctionFactory::Instance().createFunction("CompositeFunction");
284 auto composite =
285 Kernel::DynamicPointerCastHelper::dynamicPointerCastWithCheck<API::CompositeFunction, API::IFunction>(function);
286 function = API::FunctionFactory::Instance().createFunction("Gaussian");
287 auto gaussian =
288 Kernel::DynamicPointerCastHelper::dynamicPointerCastWithCheck<API::IPeakFunction, API::IFunction>(function);
289 gaussian->setCentre(initialPeak.centre);
290 gaussian->setFwhm(*initialPeak.fwhm);
291 gaussian->setHeight(initialPeak.height);
292 composite->addFunction(gaussian);
293
294 auto backgroundFunction = API::FunctionFactory::Instance().createFunction(
295 backgroundType == FLAT_BACKGROUND ? "FlatBackground" : "LinearBackground");
296 backgroundFunction->setParameter("A0", backgroundLevel);
297 if (backgroundType == LINEAR_BACKGROUND) {
298 backgroundFunction->setParameter("A1", 0.0);
299 }
300 composite->addFunction(std::move(backgroundFunction));
301
302 auto fit = createChildAlgorithm("Fit");
303 fit->setProperty("Function", std::dynamic_pointer_cast<API::IFunction>(composite));
304 fit->setProperty("InputWorkspace", profileWorkspace);
305 fit->setProperty("WorkspaceIndex", 0);
306 double const fitWindowMultiplier = getProperty(Prop::FIT_WINDOW_MULTIPLIER);
307 fit->setProperty("StartX", initialPeak.centre - fitWindowMultiplier * *initialPeak.fwhm);
308 fit->setProperty("EndX", initialPeak.centre + fitWindowMultiplier * *initialPeak.fwhm);
309 fit->setProperty("IgnoreInvalidData", true);
310 if (!isDefault(Prop::OUTPUT_FIT_WS)) {
311 fit->setProperty("Output", "__unused_find_reflectometry_lines");
312 }
313
314 try {
315 fit->execute();
316 } catch (std::exception const &error) {
317 g_log.warning() << "Specular peak fit failed: " << error.what() << ". Using the initial line centre.\n";
318 setProperty(Prop::LINE_CENTRE, initialPeak.centre);
319 setProperty(Prop::OUTPUT_STATUS, FALLBACK_STATUS);
320 return;
321 }
322
323 std::string const fitStatus = fit->getProperty("OutputStatus");
324 auto const fittedCentre = gaussian->centre();
325 auto const &profileX = profileWorkspace->x(0);
326 bool const fitSuccessful = fitStatusIsAccepted(fitStatus, getProperty(Prop::ACCEPT_CHANGES_IN_FUNCTION),
327 getProperty(Prop::ACCEPT_CHANGES_IN_PARAMETERS));
328 bool const useFittedCentreOnFailure = getProperty(Prop::USE_FITTED_CENTRE_ON_FAILURE);
329 if ((!fitSuccessful && !useFittedCentreOnFailure) || !std::isfinite(fittedCentre) ||
330 fittedCentre < profileX.front() || fittedCentre > profileX.back()) {
331 g_log.warning() << "Specular peak fit was not successful. Using the initial line centre.\n";
332 setProperty(Prop::LINE_CENTRE, initialPeak.centre);
333 setProperty(Prop::OUTPUT_STATUS, FALLBACK_STATUS);
334 return;
335 }
336
337 setProperty(Prop::LINE_CENTRE, fittedCentre);
338 setProperty(Prop::OUTPUT_STATUS, fitSuccessful ? Mantid::API::MinimizerStatus::SUCCESS : fitStatus);
339 if (!isDefault(Prop::OUTPUT_FIT_WS)) {
340 API::MatrixWorkspace_sptr fitWorkspace = fit->getProperty("OutputWorkspace");
341 setProperty(Prop::OUTPUT_FIT_WS, fitWorkspace);
342 }
343}
344
345} // namespace Mantid::Algorithms
std::string name
Definition Run.cpp:60
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:542
double centre
double background
double value
The value of the point.
Definition FitMW.cpp:51
double height
Definition GetAllEi.cpp:155
double error
IPeaksWorkspace_sptr workspace
std::map< DeltaEMode::Type, std::string > index
double left
double right
double lower
lower and upper bounds on the multiplier, if known
double upper
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.
Kernel::Logger & g_log
Definition Algorithm.h:423
bool isDefault(const std::string &name) const
Base MatrixWorkspace Abstract Class.
const HistogramData::HistogramX & x(const size_t index) const
const HistogramData::HistogramY & y(const size_t index) const
A property class for workspaces.
FindReflectometryLines3: Finds a fractional workspace index corresponding to a reflected or direct li...
std::map< std::string, std::string > validateInputs() override
Method checking errors on ALL the inputs, before execution.
const std::vector< std::string > seeAlso() const override
Function to return all of the seeAlso algorithms related to this algorithm.
API::MatrixWorkspace_sptr createProfile(const API::MatrixWorkspace_sptr &inputWorkspace)
int version() const override
function to return a version of the algorithm, must be overridden in all algorithms
static bool fitStatusIsAccepted(const std::string &fitStatus, bool acceptChangesInFunction, bool acceptChangesInParameters)
const std::string category() const override
function to return a category of the algorithm.
const std::string summary() const override
function returns a summary message that will be displayed in the default GUI, and in the help.
void init() override
Virtual method - must be overridden by concrete algorithm.
void exec() override
Virtual method - must be overridden by concrete algorithm.
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
virtual TypedValue getProperty(const std::string &name) const =0
Get the value of a property.
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
const std::string CHANGES_IN_FUNCTION_TOO_SMALL
Reported by Levenberg-Marquardt when the change in the cost function between iterations has fallen be...
const std::string CHANGES_IN_PARAMETER_TOO_SMALL
Reported by Levenberg-Marquardt when the change in the parameter values between iterations has fallen...
const std::string SUCCESS
Reported when a minimizer has fully converged.
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
Statistics getStatistics(const std::vector< TYPE > &data, const unsigned int flags=StatOptions::AllStats)
Return a statistics object for the given data set.
constexpr int EMPTY_INT() noexcept
Returns what we consider an "empty" integer within a property.
Definition EmptyValues.h:24
constexpr double EMPTY_DBL() noexcept
Returns what we consider an "empty" double within a property.
Definition EmptyValues.h:42
String constants for algorithm's properties.
STL namespace.
@ Input
An input workspace.
Definition Property.h:53
@ Output
An output workspace.
Definition Property.h:54
double median
Median value.
Definition Statistics.h:44