Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
CalculateTransmissionBeamSpreader.cpp 12.15 KiB
//----------------------------------------------------------------------
// Includes
//----------------------------------------------------------------------
#include "MantidAlgorithms/CalculateTransmissionBeamSpreader.h"
#include "MantidAPI/CommonBinsValidator.h"
#include "MantidAPI/HistogramValidator.h"
#include "MantidAPI/WorkspaceUnitValidator.h"
#include "MantidAPI/WorkspaceOpOverloads.h"
#include "MantidKernel/BoundedValidator.h"
#include "MantidKernel/CompositeValidator.h"
#include "MantidKernel/ListValidator.h"

namespace Mantid {
namespace Algorithms {

// Register the algorithm into the AlgorithmFactory
DECLARE_ALGORITHM(CalculateTransmissionBeamSpreader)

using namespace Kernel;
using namespace API;
using std::size_t;

CalculateTransmissionBeamSpreader::CalculateTransmissionBeamSpreader()
    : API::Algorithm(), logFit(false) {}

CalculateTransmissionBeamSpreader::~CalculateTransmissionBeamSpreader() {}

void CalculateTransmissionBeamSpreader::init() {
  auto wsValidator = boost::make_shared<CompositeValidator>();
  wsValidator->add<WorkspaceUnitValidator>("Wavelength");
  wsValidator->add<CommonBinsValidator>();
  wsValidator->add<HistogramValidator>();

  declareProperty(new WorkspaceProperty<>("SampleSpreaderRunWorkspace", "",
                                          Direction::Input, wsValidator),
                  "The workspace containing the sample beam-spreader run");
  declareProperty(new WorkspaceProperty<>("DirectSpreaderRunWorkspace", "",
                                          Direction::Input, wsValidator),
                  "The workspace containing the direct beam-spreader run");
  declareProperty(new WorkspaceProperty<>("SampleScatterRunWorkspace", "",
                                          Direction::Input, wsValidator),
                  "The workspace containing the sample scattering run");
  declareProperty(new WorkspaceProperty<>("DirectScatterRunWorkspace", "",
                                          Direction::Input, wsValidator),
                  "The workspace containing the direct beam scattering run");
  declareProperty(
      new WorkspaceProperty<>("OutputWorkspace", "", Direction::Output),
      "The fitted transmission correction");

  auto zeroOrMore = boost::make_shared<BoundedValidator<int>>();
  zeroOrMore->setLower(0);
  // The defaults here are the correct detector numbers for LOQ
  declareProperty("IncidentBeamMonitor", 2, zeroOrMore,
                  "The UDET of the incident beam monitor");

  auto mustBePositive = boost::make_shared<BoundedValidator<double>>();
  mustBePositive->setLower(0.0);

  declareProperty("SpreaderTransmissionValue", 1.0, mustBePositive,
                  "Transmission coefficient of the beam spreader");
  declareProperty(
      "SpreaderTransmissionError", 0.0, mustBePositive,
      "Uncertainty on the transmission coefficient of the beam spreader");

  declareProperty("MinWavelength", 2.2, mustBePositive,
                  "The minimum wavelength for the fit");
  declareProperty("MaxWavelength", 10.0, mustBePositive,
                  "The maximum wavelength for the fit");

  std::vector<std::string> options(2);
  options[0] = "Linear";
  options[1] = "Log";
  declareProperty("FitMethod", "Log",
                  boost::make_shared<StringListValidator>(options),
                  "Whether to fit directly to the transmission curve (Linear) "
                  "or to the log of it (Log)");

  declareProperty("OutputUnfittedData", false);
}

void CalculateTransmissionBeamSpreader::exec() {
  MatrixWorkspace_sptr sample_spreaderWS =
      getProperty("SampleSpreaderRunWorkspace");
  MatrixWorkspace_sptr direct_spreaderWS =
      getProperty("DirectSpreaderRunWorkspace");
  MatrixWorkspace_sptr sample_scatterWS =
      getProperty("SampleScatterRunWorkspace");
  MatrixWorkspace_sptr direct_scatterWS =
      getProperty("DirectScatterRunWorkspace");

  // Check that the two input workspaces are from the same instrument
  if (sample_spreaderWS->getInstrument()->getName() !=
          direct_spreaderWS->getInstrument()->getName() ||
      sample_spreaderWS->getInstrument()->getName() !=
          sample_scatterWS->getInstrument()->getName() ||
      sample_spreaderWS->getInstrument()->getName() !=
          direct_scatterWS->getInstrument()->getName()) {
    g_log.error("The input workspaces do not come from the same instrument");
    throw std::invalid_argument(
        "The input workspaces do not come from the same instrument");
  }
  // Check that the two inputs have matching binning
  if (!WorkspaceHelpers::matchingBins(sample_spreaderWS, direct_spreaderWS) ||
      !WorkspaceHelpers::matchingBins(sample_spreaderWS, sample_scatterWS) ||
      !WorkspaceHelpers::matchingBins(sample_spreaderWS, direct_scatterWS)) {
    g_log.error("Input workspaces do not have matching binning");
    throw std::invalid_argument(
        "Input workspaces do not have matching binning");
  }

  // Extract the required spectra into separate workspaces
  std::vector<detid_t> udets;
  std::vector<size_t> indices;
  udets.push_back(getProperty("IncidentBeamMonitor"));

  // Convert UDETs to workspace indices
  // Get monitors (assume that the detector mapping is the same for all data
  // sets)
  sample_scatterWS->getIndicesFromDetectorIDs(udets, indices);
  if (indices.size() != 1) {
    g_log.error() << "Could not find the incident monitor spectra\n";
    throw std::invalid_argument(
        "Could not find the incident monitor spectra\n");
  }

  MatrixWorkspace_sptr sample_scatter_mon =
      this->extractSpectrum(sample_scatterWS, indices[0]);
  MatrixWorkspace_sptr direct_scatter_mon =
      this->extractSpectrum(direct_scatterWS, indices[0]);
  MatrixWorkspace_sptr sample_spreader_mon =
      this->extractSpectrum(sample_spreaderWS, indices[0]);
  MatrixWorkspace_sptr direct_spreader_mon =
      this->extractSpectrum(direct_spreaderWS, indices[0]);

  // Sum the whole detector for each of the four data sets
  MatrixWorkspace_sptr sample_scatter_sum;
  MatrixWorkspace_sptr direct_scatter_sum;
  MatrixWorkspace_sptr sample_spreader_sum;
  MatrixWorkspace_sptr direct_spreader_sum;
  // Note: Replaced PARALLEL_SECTION with this OMP for loop, due to occasional
  // unexplained segfault.
  std::vector<MatrixWorkspace_sptr> in_ws, out_ws(4);
  in_ws.push_back(sample_scatterWS);
  in_ws.push_back(direct_scatterWS);
  in_ws.push_back(sample_spreaderWS);
  in_ws.push_back(direct_spreaderWS);

  PARALLEL_FOR_IF(true)
  for (int i = 0; i < 4; i++) {
    out_ws[i] = this->sumSpectra(in_ws[i]);
  }
  sample_scatter_sum = out_ws[0];
  direct_scatter_sum = out_ws[1];
  sample_spreader_sum = out_ws[2];
  direct_spreader_sum = out_ws[3];

  // Beam spreader transmission
  MatrixWorkspace_sptr spreader_trans =
      WorkspaceFactory::Instance().create("WorkspaceSingleValue", 1, 1, 1);
  spreader_trans->setYUnit("");
  spreader_trans->isDistribution(true);
  spreader_trans->dataX(0)[0] = 0.0;
  spreader_trans->dataY(0)[0] = getProperty("SpreaderTransmissionValue");
  spreader_trans->dataE(0)[0] = getProperty("SpreaderTransmissionError");

  // The main calculation
  MatrixWorkspace_sptr numerator =
      sample_spreader_sum / sample_spreader_mon -
      spreader_trans * sample_scatter_sum / sample_scatter_mon;

  MatrixWorkspace_sptr denominator =
      direct_spreader_sum / direct_spreader_mon -
      spreader_trans * direct_scatter_sum / direct_scatter_mon;

  MatrixWorkspace_sptr transmission = numerator / denominator;

  // Output this data if requested
  const bool outputRaw = getProperty("OutputUnfittedData");
  if (outputRaw) {
    std::string outputWSName = getPropertyValue("OutputWorkspace");
    outputWSName += "_unfitted";
    declareProperty(new WorkspaceProperty<>("UnfittedData", outputWSName,
                                            Direction::Output));
    setProperty("UnfittedData", transmission);
  }

  // Check that there are more than a single bin in the transmission
  // workspace. Skip the fit it there isn't.
  if (transmission->dataY(0).size() == 1) {
    setProperty("OutputWorkspace", transmission);
  } else {
    MatrixWorkspace_sptr fit;
    const std::string fitMethod = getProperty("FitMethod");
    logFit = (fitMethod == "Log");
    if (logFit) {
      g_log.debug("Fitting to the logarithm of the transmission");
      // Take a copy of this workspace for the fitting
      MatrixWorkspace_sptr logTransmission =
          this->extractSpectrum(transmission, 0);

      // Take the log of each datapoint for fitting. Preserve errors
      // percentage-wise.
      MantidVec &Y = logTransmission->dataY(0);
      MantidVec &E = logTransmission->dataE(0);
      Progress progress(this, 0.4, 0.6, Y.size());
      for (size_t i = 0; i < Y.size(); ++i) {
        E[i] = std::abs(E[i] / Y[i]);
        Y[i] = std::log10(Y[i]);
        progress.report("Calculate Transmission");
      }

      // Now fit this to a straight line
      fit = this->fitToData(logTransmission);
    } // logFit true
    else {
      g_log.debug("Fitting directly to the data (i.e. linearly)");
      fit = this->fitToData(transmission);
    }

    setProperty("OutputWorkspace", fit);
  }
}

/** Sum all detector pixels except monitors and masked detectors
 *  @param WS ::    The workspace containing the spectrum to sum
 *  @return A Workspace2D containing the sum
 */
API::MatrixWorkspace_sptr
CalculateTransmissionBeamSpreader::sumSpectra(API::MatrixWorkspace_sptr WS) {
  Algorithm_sptr childAlg = createChildAlgorithm("SumSpectra");
  childAlg->setProperty<MatrixWorkspace_sptr>("InputWorkspace", WS);
  childAlg->setProperty<bool>("IncludeMonitors", false);
  childAlg->executeAsChildAlg();
  return childAlg->getProperty("OutputWorkspace");
}

/** Extracts a single spectrum from a Workspace2D into a new workspaces. Uses
 * CropWorkspace to do this.
 *  @param WS ::    The workspace containing the spectrum to extract
 *  @param index :: The workspace index of the spectrum to extract
 *  @return A Workspace2D containing the extracted spectrum
 */
API::MatrixWorkspace_sptr
CalculateTransmissionBeamSpreader::extractSpectrum(API::MatrixWorkspace_sptr WS,
                                                   const size_t index) {
  // Check that given spectra are monitors
  if (!WS->getDetector(index)->isMonitor()) {
    g_log.information(
        "The Incident Beam Monitor UDET provided is not marked as a monitor");
  }

  Algorithm_sptr childAlg =
      createChildAlgorithm("ExtractSingleSpectrum", 0.0, 0.4);
  childAlg->setProperty<MatrixWorkspace_sptr>("InputWorkspace", WS);
  childAlg->setProperty<int>("WorkspaceIndex", static_cast<int>(index));
  childAlg->executeAsChildAlg();
  return childAlg->getProperty("OutputWorkspace");
}

/** Uses 'Linear' as a ChildAlgorithm to fit the log of the exponential curve
 * expected for the transmission.
 *  @param WS :: The single-spectrum workspace to fit
 *  @return A workspace containing the fit
 */
API::MatrixWorkspace_sptr
CalculateTransmissionBeamSpreader::fitToData(API::MatrixWorkspace_sptr WS) {
  g_log.information("Fitting the experimental transmission curve");
  Algorithm_sptr childAlg = createChildAlgorithm("Linear", 0.6, 1.0);
  childAlg->setProperty<MatrixWorkspace_sptr>("InputWorkspace", WS);
  const double lambdaMin = getProperty("MinWavelength");
  const double lambdaMax = getProperty("MaxWavelength");
  childAlg->setProperty<double>("StartX", lambdaMin);
  childAlg->setProperty<double>("EndX", lambdaMax);
  childAlg->executeAsChildAlg();

  std::string fitStatus = childAlg->getProperty("FitStatus");
  if (fitStatus != "success") {
    g_log.error("Unable to successfully fit the data: " + fitStatus);
    throw std::runtime_error("Unable to successfully fit the data");
  }

  // Only get to here if successful
  MatrixWorkspace_sptr result = childAlg->getProperty("OutputWorkspace");

  if (logFit) {
    // Need to transform back to 'unlogged'
    double b = childAlg->getProperty("FitIntercept");
    double m = childAlg->getProperty("FitSlope");
    b = std::pow(10, b);
    m = std::pow(10, m);

    const MantidVec &X = result->readX(0);
    MantidVec &Y = result->dataY(0);
    MantidVec &E = result->dataE(0);
    for (size_t i = 0; i < Y.size(); ++i) {
      Y[i] = b * (std::pow(m, 0.5 * (X[i] + X[i + 1])));
      E[i] = std::abs(E[i] * Y[i]);
    }
  }

  return result;
}

} // namespace Algorithm
} // namespace Mantid