-
Anthony Lim authoredAnthony Lim authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
CalculateMuonAsymmetry.cpp 10.69 KiB
//----------------------------------------------------------------------
// Includes
//----------------------------------------------------------------------
#include "MantidAlgorithms/CalculateMuonAsymmetry.h"
#include "MantidAlgorithms/MuonAsymmetryHelper.h"
#include "MantidAPI/AlgorithmManager.h"
#include "MantidAPI/FuncMinimizerFactory.h"
#include "MantidAPI/IFuncMinimizer.h"
#include "MantidAPI/IFunction.h"
#include "MantidAPI/MatrixWorkspace.h"
#include "MantidAPI/Run.h"
#include "MantidAPI/Workspace_fwd.h"
#include "MantidAPI/WorkspaceFactory.h"
#include "MantidKernel/ArrayProperty.h"
#include "MantidKernel/BoundedValidator.h"
#include "MantidKernel/ListValidator.h"
#include "MantidKernel/PhysicalConstants.h"
#include "MantidKernel/StartsWithValidator.h"
#include <cmath>
#include <numeric>
#include <vector>
namespace Mantid {
namespace Algorithms {
using namespace Kernel;
using API::Progress;
using std::size_t;
// Register the class into the algorithm factory
DECLARE_ALGORITHM(CalculateMuonAsymmetry)
/** Initialisation method. Declares properties to be used in algorithm.
*
*/
void CalculateMuonAsymmetry::init() {
declareProperty(make_unique<API::WorkspaceProperty<API::MatrixWorkspace>>(
"InputWorkspace", "", Direction::Input),
"The name of the input 2D workspace.");
declareProperty(make_unique<API::WorkspaceProperty<API::MatrixWorkspace>>(
"OutputWorkspace", "", Direction::Output),
"The name of the output 2D workspace.");
std::vector<int> empty;
declareProperty(
Kernel::make_unique<Kernel::ArrayProperty<int>>("Spectra", empty),
"The workspace indices to remove the exponential decay from.");
declareProperty(
"StartX", 0.1,
"The lower limit for calculating the asymmetry (an X value).");
declareProperty(
"EndX", 15.0,
"The upper limit for calculating the asymmetry (an X value).");
declareProperty(
"FittingFunction",
"name = GausOsc, A = 10.0, Sigma = 0.2, Frequency = 1.0, Phi = 0.0",
"The additional fitting functions to be used.");
declareProperty("InputDataType", "counts",
boost::make_shared<Mantid::Kernel::StringListValidator>(
std::vector<std::string>{"counts", "asymmetry"}),
"If the data is raw counts or asymmetry");
std::vector<std::string> minimizerOptions =
API::FuncMinimizerFactory::Instance().getKeys();
Kernel::IValidator_sptr minimizerValidator =
boost::make_shared<Kernel::StartsWithValidator>(minimizerOptions);
declareProperty("Minimizer", "Levenberg-MarquardtMD", minimizerValidator,
"Minimizer to use for fitting.");
auto mustBePositive = boost::make_shared<Kernel::BoundedValidator<int>>();
mustBePositive->setLower(0);
declareProperty(
"MaxIterations", 500, mustBePositive->clone(),
"Stop after this number of iterations if a good fit is not found");
declareProperty(Kernel::make_unique<Kernel::ArrayProperty<double>>(
"NormalizationConstant", Direction::Output));
std::vector<double> emptyDoubles;
declareProperty(Kernel::make_unique<Kernel::ArrayProperty<double>>(
"PreviousNormalizationConstant", emptyDoubles),
"Normalization constant used"
" to estimate asymmetry");
}
/*
* Validate the input parameters
* @returns map with keys corresponding to properties with errors and values
* containing the error messages.
*/
std::map<std::string, std::string> CalculateMuonAsymmetry::validateInputs() {
// create the map
std::map<std::string, std::string> validationOutput;
// check start and end times
double startX = getProperty("StartX");
double endX = getProperty("EndX");
if (startX > endX) {
validationOutput["StartX"] = "Start time is after the end time.";
} else if (startX == endX) {
validationOutput["StartX"] = "Start and end times are equal, there is no "
"data to apply the algorithm to.";
}
std::string dataType = getProperty("InputDataType");
std::vector<double> oldNorm = getProperty("PreviousNormalizationConstant");
std::vector<int> spectra = getProperty("Spectra");
if (dataType == "asymmetry" && oldNorm.empty()) {
validationOutput["PreviousNormalizationConstant"] =
"Asymmetry data has been provided but"
" no normalization constants have been provided.";
}
if (dataType == "asymmetry" && oldNorm.size() != spectra.size()) {
validationOutput["PreviousNormalizationConstant"] =
"Number of spectra and the list"
" of normalization constants are inconsistant.";
}
return validationOutput;
}
/** Executes the algorithm
*
*/
void CalculateMuonAsymmetry::exec() {
std::vector<int> spectra = getProperty("Spectra");
std::vector<double> oldNorm = getProperty("PreviousNormalizationConstant");
// Get original workspace
API::MatrixWorkspace_const_sptr inputWS = getProperty("InputWorkspace");
auto numSpectra = inputWS->getNumberHistograms();
// Create output workspace with same dimensions as input
API::MatrixWorkspace_sptr outputWS = getProperty("OutputWorkspace");
if (inputWS != outputWS) {
outputWS = API::WorkspaceFactory::Instance().create(inputWS);
}
double startX = getProperty("StartX");
double endX = getProperty("EndX");
auto dataType = getPropertyValue("InputDataType");
const Mantid::API::Run &run = inputWS->run();
const double numGoodFrames = std::stod(run.getProperty("goodfrm")->value());
g_log.warning("Assuming that the spectra and normalization constants are in "
"the same order");
// Share the X values
for (size_t i = 0; i < static_cast<size_t>(numSpectra); ++i) {
outputWS->setSharedX(i, inputWS->sharedX(i));
}
// No spectra specified = process all spectra
if (spectra.empty()) {
spectra = std::vector<int>(numSpectra);
std::iota(spectra.begin(), spectra.end(), 0);
}
Progress prog(this, 0.0, 1.0, numSpectra + spectra.size());
if (inputWS != outputWS) {
// Copy all the Y and E data
PARALLEL_FOR_IF(Kernel::threadSafe(*inputWS, *outputWS))
for (int64_t i = 0; i < int64_t(numSpectra); ++i) {
PARALLEL_START_INTERUPT_REGION
const auto index = static_cast<size_t>(i);
outputWS->setSharedY(index, inputWS->sharedY(index));
outputWS->setSharedE(index, inputWS->sharedE(index));
prog.report();
PARALLEL_END_INTERUPT_REGION
}
PARALLEL_CHECK_INTERUPT_REGION
}
// Do the specified spectra only
int specLength = static_cast<int>(spectra.size());
std::vector<double> norm(specLength, 0.0);
PARALLEL_FOR_IF(Kernel::threadSafe(*inputWS, *outputWS))
for (int i = 0; i < specLength; ++i) {
PARALLEL_START_INTERUPT_REGION
const auto specNum = static_cast<size_t>(spectra[i]);
if (spectra[i] > static_cast<int>(numSpectra)) {
g_log.error("The spectral index " + std::to_string(spectra[i]) +
" is greater than the number of spectra!");
throw std::invalid_argument("The spectral index " +
std::to_string(spectra[i]) +
" is greater than the number of spectra!");
}
double estNormConst;
if (dataType == "counts") {
// inital estimate of N0
estNormConst = estimateNormalisationConst(inputWS->histogram(specNum),
numGoodFrames, startX, endX);
// Calculate the normalised counts
outputWS->setHistogram(
specNum, normaliseCounts(inputWS->histogram(specNum), numGoodFrames));
} else {
estNormConst = oldNorm[i];
outputWS->setHistogram(specNum, inputWS->histogram(specNum));
// convert the data back to normalised counts
outputWS->mutableY(specNum) += 1.0;
outputWS->mutableY(specNum) *= estNormConst;
outputWS->mutableE(specNum) *= estNormConst;
}
// get the normalisation constant
const double normConst =
getNormConstant(outputWS, spectra[i], estNormConst, startX, endX);
// calculate the asymmetry
outputWS->mutableY(specNum) /= normConst;
outputWS->mutableY(specNum) -= 1.0;
outputWS->mutableE(specNum) /= normConst;
norm[i] = normConst;
prog.report();
PARALLEL_END_INTERUPT_REGION
}
PARALLEL_CHECK_INTERUPT_REGION
// Update Y axis units
outputWS->setYUnit("Asymmetry");
setProperty("NormalizationConstant", norm);
setProperty("OutputWorkspace", outputWS);
}
/**
* Calculate normalisation constant after the exponential decay has been removed
* to a linear fitting function
* @param ws :: workspace
* @param wsIndex :: workspace index
* @param estNormConstant :: estimate of normalisation constant
* @param startX :: the smallest x value for the fit
* @param endX :: the largest x value for the fit
* @return normalisation constant
*/
double CalculateMuonAsymmetry::getNormConstant(API::MatrixWorkspace_sptr ws,
int wsIndex,
const double estNormConstant,
const double startX,
const double endX) {
double retVal = 1.0;
int maxIterations = getProperty("MaxIterations");
auto minimizer = getProperty("Minimizer");
API::IAlgorithm_sptr fit;
fit = createChildAlgorithm("Fit", -1, -1, true);
std::string tmpString = getProperty("FittingFunction");
std::string function;
function = "composite=ProductFunction;name=FlatBackground,A0=" +
std::to_string(estNormConstant);
function += ";(name=FlatBackground,A0=1.0,ties=(A0=1.0);";
function += tmpString + ")";
fit->setProperty("MaxIterations", maxIterations);
fit->setPropertyValue("Function", function);
fit->setProperty("InputWorkspace", ws);
fit->setProperty("WorkspaceIndex", wsIndex);
fit->setPropertyValue("Minimizer", minimizer);
fit->setProperty("StartX", startX);
fit->setProperty("EndX", endX);
fit->execute();
std::string fitStatus = fit->getProperty("OutputStatus");
API::IFunction_sptr result = fit->getProperty("Function");
const double A0 = result->getParameter(0);
if (fitStatus == "success") { // to be explicit
if (A0 < 0) {
g_log.warning() << "When trying to fit Asymmetry normalisation constant "
"this constant comes out negative. For workspace "
<< wsIndex << "."
<< "To proceed Asym norm constant set to 1.0\n";
retVal = 1.0;
} else {
retVal = A0;
}
} else {
g_log.warning() << "Fit falled. Status = " << fitStatus
<< "\nFor workspace index " << wsIndex
<< "\nAsym norm constant set to 1.0\n";
retVal = 1.0;
}
return retVal;
}
} // namespace Algorithm
} // namespace Mantid