Skip to content

Commit c319287

Browse files
authored
Correct error propagation on PolarizationEfficienciesWildes algorithm (#39300)
### Description of work #### Summary of work This PR corrects the error propagation for the `PolarizationEfficienciesWildes` algorithm. As part of this work a class `ErrorPropagation` has been added to `PolarizationCorrectionsHelpers.h`. This generic class utilizes the eigen `AutoDiff` module to automatically calculate the partials for different input variables. If there is wider interest in this class I will move it into the `kernel` in a separate PR. The `PolarizationEfficienciesWildes` algorithm has been refactored slightly to enable use of the `ErrorPropagation` class and to remove the use of derived quantities in workspace arithmetic. The theory behind the approach to error propagation can be read: #37768 Fixes #37768 ### To test: 1) Check all unit tests pass. As unit tests regarding the values of the derived parameters were implemented, this proves that the calculation of the values of these parameters is unchanged. 2) Check unit tests for the `ErrorPropagation` class in `PolarizationCorrectionsHelpersTest`. These include simple examples `testErrorPropagation_linear`, `testErrorPropagation_quad` and `testErrorPropagation_mult_vars` that prove the function of the class. You can check these on paper/using wolfram alpha. The latter test I copied from this blog, obtaining the same results: https://michaelspieler.com/post/eigen-autodiff/ 3) Script to run `PolarizationEfficienciesWildes` on some real (system test) data: ``` from mantid.simpleapi import * Load(Filename=r'POLREF00032130.nxs', OutputWorkspace='POLREF00032130') ConvertUnits(InputWorkspace='POLREF00032130', OutputWorkspace='POLREF00032130_y', Target='Wavelength', ConvertFromPointData=False) Rebin(InputWorkspace='POLREF00032130_y', OutputWorkspace='POLREF00032130_y_rb', Params='0.01', PreserveEvents=False) SumSpectra(InputWorkspace='POLREF00032130_y_rb', OutputWorkspace='POLREF00032130_y_rb_sum') PolarizationEfficienciesWildes(InputNonMagWorkspace='POLREF00032130_y_rb_sum', OutputFpEfficiency='fp', OutputFaEfficiency='fa', IncludeDiagnosticOutputs=True) ``` 4) Check error values output are sensible (however you determine that!)
1 parent d8a40fa commit c319287

File tree

9 files changed

+378
-119
lines changed

9 files changed

+378
-119
lines changed

Framework/Algorithms/inc/MantidAlgorithms/PolarizationCorrections/PolarizationCorrectionsHelpers.h

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@
77

88
#pragma once
99

10+
#include "MantidAPI/AlgorithmManager.h"
1011
#include "MantidAPI/MatrixWorkspace.h"
1112
#include "MantidAPI/WorkspaceGroup.h"
1213
#include "MantidAlgorithms/DllConfig.h"
14+
#include "MantidKernel/MultiThreaded.h"
15+
#include <Eigen/Dense>
1316
#include <optional>
17+
#include <unsupported/Eigen/AutoDiff>
1418

1519
namespace Mantid::Algorithms {
1620
namespace PolarizationCorrectionsHelpers {
@@ -66,4 +70,109 @@ MANTID_ALGORITHMS_DLL const std::string &getORSONotationForSpinState(const std::
6670
MANTID_ALGORITHMS_DLL void addORSOLogForSpinState(const Mantid::API::MatrixWorkspace_sptr &ws,
6771
const std::string &spinState);
6872
} // namespace SpinStatesORSO
73+
74+
namespace Arithmetic {
75+
76+
template <size_t N> class ErrorTypeHelper {
77+
public:
78+
using DerType = Eigen::Matrix<double, N, 1>;
79+
using InputArray = DerType;
80+
using ADScalar = Eigen::AutoDiffScalar<DerType>;
81+
};
82+
83+
template <size_t N, typename Func> class ErrorPropagation {
84+
public:
85+
using Types = ErrorTypeHelper<N>;
86+
using DerType = Types::DerType;
87+
using ADScalar = Types::ADScalar;
88+
using InputArray = Types::InputArray;
89+
ErrorPropagation(Func func) : computeFunc(std::move(func)) {}
90+
91+
struct AutoDevResult {
92+
double value;
93+
double error;
94+
Eigen::Array<double, N, 1> derivatives;
95+
};
96+
97+
AutoDevResult evaluate(const InputArray &values, const InputArray &errors) const {
98+
std::array<ADScalar, N> x;
99+
for (size_t i = 0; i < N; ++i) {
100+
x[i] = ADScalar(values[i], DerType::Unit(N, i));
101+
}
102+
const ADScalar y = computeFunc(x);
103+
const auto &derivatives = y.derivatives();
104+
return {y.value(), std::sqrt((derivatives.array().square() * errors.array().square()).sum()), derivatives};
105+
}
106+
107+
template <std::same_as<API::MatrixWorkspace_sptr>... Ts>
108+
API::MatrixWorkspace_sptr evaluateWorkspaces(const bool outputWorkspaceDistribution, Ts... args) const {
109+
return evaluateWorkspacesImpl(outputWorkspaceDistribution, std::forward<Ts>(args)...);
110+
}
111+
112+
template <std::same_as<API::MatrixWorkspace_sptr>... Ts>
113+
API::MatrixWorkspace_sptr evaluateWorkspaces(Ts... args) const {
114+
return evaluateWorkspacesImpl(std::nullopt, std::forward<Ts>(args)...);
115+
}
116+
117+
private:
118+
Func computeFunc;
119+
120+
template <std::same_as<API::MatrixWorkspace_sptr>... Ts>
121+
API::MatrixWorkspace_sptr evaluateWorkspacesImpl(std::optional<bool> outputWorkspaceDistribution, Ts... args) const {
122+
const auto firstWs = std::get<0>(std::forward_as_tuple(args...));
123+
API::MatrixWorkspace_sptr outWs = firstWs->clone();
124+
125+
if (outWs->id() == "EventWorkspace") {
126+
outWs = convertToWorkspace2D(outWs);
127+
}
128+
129+
const size_t numSpec = outWs->getNumberHistograms();
130+
const size_t specSize = outWs->blocksize();
131+
132+
// cppcheck-suppress unreadVariable
133+
const bool isThreadSafe = Kernel::threadSafe((*args)..., *outWs);
134+
// cppcheck-suppress unreadVariable
135+
const bool specOverBins = numSpec > specSize;
136+
137+
PARALLEL_FOR_IF(isThreadSafe && specOverBins)
138+
for (int64_t i = 0; i < static_cast<int64_t>(numSpec); i++) {
139+
auto &yOut = outWs->mutableY(i);
140+
auto &eOut = outWs->mutableE(i);
141+
142+
PARALLEL_FOR_IF(isThreadSafe && !specOverBins)
143+
for (int64_t j = 0; j < static_cast<int64_t>(specSize); ++j) {
144+
const auto result = evaluate(InputArray{args->y(i)[j]...}, InputArray(args->e(i)[j]...));
145+
yOut[j] = result.value;
146+
eOut[j] = result.error;
147+
}
148+
}
149+
150+
if (outputWorkspaceDistribution.has_value()) {
151+
outWs->setDistribution(outputWorkspaceDistribution.value());
152+
}
153+
return outWs;
154+
}
155+
156+
API::MatrixWorkspace_sptr runWorkspaceConversionAlg(const API::MatrixWorkspace_sptr &workspace,
157+
const std::string &algName) const {
158+
auto conversionAlg = API::AlgorithmManager::Instance().create(algName);
159+
conversionAlg->initialize();
160+
conversionAlg->setChild(true);
161+
conversionAlg->setProperty("InputWorkspace", workspace);
162+
conversionAlg->setProperty("OutputWorkspace", workspace->getName());
163+
conversionAlg->execute();
164+
return conversionAlg->getProperty("OutputWorkspace");
165+
}
166+
167+
API::MatrixWorkspace_sptr convertToWorkspace2D(const API::MatrixWorkspace_sptr &workspace) const {
168+
runWorkspaceConversionAlg(workspace, "ConvertToHistogram");
169+
return runWorkspaceConversionAlg(workspace, "ConvertToMatrixWorkspace");
170+
}
171+
};
172+
173+
template <size_t N, typename Func> auto makeErrorPropagation(Func &&func) {
174+
return ErrorPropagation<N, std::decay_t<Func>>(std::forward<Func>(func));
175+
}
176+
177+
} // namespace Arithmetic
69178
} // namespace Mantid::Algorithms

Framework/Algorithms/inc/MantidAlgorithms/PolarizationCorrections/PolarizationEfficienciesWildes.h

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "MantidAPI/MatrixWorkspace.h"
1111
#include "MantidAPI/WorkspaceGroup.h"
1212
#include "MantidAlgorithms/DllConfig.h"
13+
#include <unordered_map>
1314

1415
namespace Mantid::Algorithms {
1516

@@ -45,19 +46,12 @@ class MANTID_ALGORITHMS_DLL PolarizationEfficienciesWildes : public API::Algorit
4546
/// Calculate Fp, Fa and Phi
4647
void calculateFlipperEfficienciesAndPhi();
4748

48-
/// Calculate (2p-1) from Phi, Fp, Fa and the magnetic workspace intensities
49-
MatrixWorkspace_sptr calculateTPMOFromPhi(const WorkspaceGroup_sptr &magWsGrp);
49+
/// Calculate (2p-1) from intensities
50+
MatrixWorkspace_sptr calculateTPMO();
5051

5152
/// Calculate the polarizer and/or analyser efficiencies, as requested
5253
void calculatePolarizerAndAnalyserEfficiencies(const bool solveForP, const bool solveForA);
5354

54-
/// If either the polarizer or the analyser efficiency is known, use the relationship Phi = (2p-1)(2a-1) to solve for
55-
/// the other efficiency
56-
MatrixWorkspace_sptr solveForUnknownEfficiency(const MatrixWorkspace_sptr &knownEfficiency);
57-
58-
/// Solve for the unknown efficiency from either (2p-1) or (2a-1) using the relationship Phi = (2p-1)(2a-1)
59-
MatrixWorkspace_sptr solveUnknownEfficiencyFromTXMO(const MatrixWorkspace_sptr &wsTXMO);
60-
6155
/// Set the algorithm outputs
6256
void setOutputs();
6357

@@ -68,10 +62,29 @@ class MANTID_ALGORITHMS_DLL PolarizationEfficienciesWildes : public API::Algorit
6862
/// held by the property
6963
void resetPropertyValue(const std::string &propertyName);
7064

65+
/// Populates the spin state workspaces map from ws group given key prefix.
66+
void populateSpinStateWorkspaces(const WorkspaceGroup_sptr &wsGrp, const std::string &keyPrefix = "");
67+
68+
/// Populates the spin state workspaces map
69+
void mapSpinStateWorkspaces();
70+
71+
// Convenience struct for handling of flipper workspaces
72+
struct FlipperWorkspaces {
73+
const MatrixWorkspace_sptr &ws00;
74+
const MatrixWorkspace_sptr &ws01;
75+
const MatrixWorkspace_sptr &ws10;
76+
const MatrixWorkspace_sptr &ws11;
77+
};
78+
79+
/// Access flipper workspaces in the spin state workspaces map
80+
FlipperWorkspaces getFlipperWorkspaces(const bool mag = false);
81+
7182
MatrixWorkspace_sptr m_wsFp = nullptr;
7283
MatrixWorkspace_sptr m_wsFa = nullptr;
7384
MatrixWorkspace_sptr m_wsPhi = nullptr;
7485
MatrixWorkspace_sptr m_wsP = nullptr;
7586
MatrixWorkspace_sptr m_wsA = nullptr;
87+
std::unordered_map<std::string, MatrixWorkspace_sptr> m_spinStateWorkspaces;
88+
bool m_magWsProvided = false;
7689
};
7790
} // namespace Mantid::Algorithms

0 commit comments

Comments
 (0)