Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
MaxEnt.cpp 27.15 KiB
#include "MantidAlgorithms/MaxEnt.h"
#include "MantidAPI/EqualBinSizesValidator.h"
#include "MantidAPI/MatrixWorkspace.h"
#include "MantidAPI/TextAxis.h"
#include "MantidAPI/WorkspaceFactory.h"
#include "MantidAlgorithms/MaxEnt/MaxentEntropyNegativeValues.h"
#include "MantidAlgorithms/MaxEnt/MaxentEntropyPositiveValues.h"
#include "MantidAlgorithms/MaxEnt/MaxentSpaceComplex.h"
#include "MantidAlgorithms/MaxEnt/MaxentSpaceReal.h"
#include "MantidAlgorithms/MaxEnt/MaxentTransformFourier.h"
#include "MantidHistogramData/LinearGenerator.h"
#include "MantidKernel/BoundedValidator.h"
#include "MantidKernel/ListValidator.h"
#include "MantidKernel/UnitFactory.h"
#include "MantidKernel/VectorHelper.h"
#include <gsl/gsl_linalg.h>
#include <numeric>

namespace Mantid {
namespace Algorithms {

using Mantid::HistogramData::LinearGenerator;
using Mantid::HistogramData::Points;

using namespace API;
using namespace Kernel;

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

namespace {

// Maps defining the inverse caption and label for the reconstructed image
// Example:
// The input workspaces (X axis) is in (Time, s)
// The output image should be in (Frequency, Hz)

// Defines the new caption
std::map<std::string, std::string> inverseCaption = {{"Time", "Frequency"},
                                                     {"Frequency", "Time"},
                                                     {"d-Spacing", "q"},
                                                     {"q", "d-Spacing"}};
// Defines the new label
std::map<std::string, std::string> inverseLabel = {{"s", "Hz"},
                                                   {"microsecond", "MHz"},
                                                   {"Hz", "s"},
                                                   {"MHz", "microsecond"},
                                                   {"Angstrom", "Angstrom^-1"},
                                                   {"Angstrom^-1", "Angstrom"}};
}

//----------------------------------------------------------------------------------------------

/// Algorithm's name for identification. @see Algorithm::name
const std::string MaxEnt::name() const { return "MaxEnt"; }

/// Algorithm's version for identification. @see Algorithm::version
int MaxEnt::version() const { return 1; }

/// Algorithm's category for identification. @see Algorithm::category
const std::string MaxEnt::category() const { return "Arithmetic\\FFT"; }

/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary
const std::string MaxEnt::summary() const {
  return "Runs Maximum Entropy method on every spectrum of an input workspace. "
         "It currently works for the case where data and image are related by a"
         " 1D Fourier transform.";
}

//----------------------------------------------------------------------------------------------
/** Initialize the algorithm's properties.
 */
void MaxEnt::init() {

  // X values in input workspace must be (almost) equally spaced
  const double warningLevel = 0.01;
  const double errorLevel = 0.5;
  declareProperty(
      make_unique<WorkspaceProperty<>>(
          "InputWorkspace", "", Direction::Input,
          boost::make_shared<EqualBinSizesValidator>(errorLevel, warningLevel)),
      "An input workspace.");

  declareProperty("ComplexData", false,
                  "If true, the input data is assumed to be complex and the "
                  "input workspace is expected to have an even number of "
                  "histograms (2N). Spectrum numbers S and S+N are assumed to "
                  "be the real and imaginary part of the complex signal "
                  "respectively.");

  declareProperty("ComplexImage", true,
                  "If true, the algorithm will use complex images for the "
                  "calculations. This is the recommended option when there is "
                  "no prior knowledge about the image. If the image is known "
                  "to be real, this option can be set to false and the "
                  "algorithm will only consider the real part for "
                  "calculations.");

  declareProperty("PositiveImage", false,
                  "If true, the reconstructed image is only allowed to take "
                  "positive values. It can take negative values otherwise. "
                  "This option defines the entropy formula that will be used "
                  "for the calculations (see next section for more details).");

  declareProperty("AutoShift", false,
                  "Automatically calculate and apply phase shift. Zero on the "
                  "X axis is assumed to be in the first bin. If it is not, "
                  "setting this property will automatically correct for this.");

  auto mustBePositive = boost::make_shared<BoundedValidator<size_t>>();
  mustBePositive->setLower(0);
  declareProperty(make_unique<PropertyWithValue<size_t>>(
                      "ResolutionFactor", 1, mustBePositive, Direction::Input),
                  "An integer number indicating the factor by which the number "
                  "of points will be increased in the image and reconstructed "
                  "data");

  auto mustBeNonNegative = boost::make_shared<BoundedValidator<double>>();
  mustBeNonNegative->setLower(1E-12);
  declareProperty(make_unique<PropertyWithValue<double>>(
                      "A", 0.4, mustBeNonNegative, Direction::Input),
                  "A maximum entropy constant");

  declareProperty(make_unique<PropertyWithValue<double>>(
                      "ChiTarget", 100.0, mustBeNonNegative, Direction::Input),
                  "Target value of Chi-square");

  declareProperty(make_unique<PropertyWithValue<double>>(
                      "ChiEps", 0.001, mustBeNonNegative, Direction::Input),
                  "Required precision for Chi-square");

  declareProperty(
      make_unique<PropertyWithValue<double>>(
          "DistancePenalty", 0.1, mustBeNonNegative, Direction::Input),
      "Distance penalty applied to the current image at each iteration.");

  declareProperty(make_unique<PropertyWithValue<double>>(
                      "MaxAngle", 0.05, mustBeNonNegative, Direction::Input),
                  "Maximum degree of non-parallelism between S and C.");
  mustBePositive = boost::make_shared<BoundedValidator<size_t>>();
  mustBePositive->setLower(1);
  declareProperty(make_unique<PropertyWithValue<size_t>>(
                      "MaxIterations", 20000, mustBePositive, Direction::Input),
                  "Maximum number of iterations.");

  declareProperty(make_unique<PropertyWithValue<size_t>>("AlphaChopIterations",
                                                         500, mustBePositive,
                                                         Direction::Input),
                  "Maximum number of iterations in alpha chop.");

  declareProperty(
      make_unique<WorkspaceProperty<>>("EvolChi", "", Direction::Output),
      "Output workspace containing the evolution of Chi-sq.");
  declareProperty(
      make_unique<WorkspaceProperty<>>("EvolAngle", "", Direction::Output),
      "Output workspace containing the evolution of "
      "non-paralellism between S and C.");
  declareProperty(make_unique<WorkspaceProperty<>>("ReconstructedImage", "",
                                                   Direction::Output),
                  "The output workspace containing the reconstructed image.");
  declareProperty(make_unique<WorkspaceProperty<>>("ReconstructedData", "",
                                                   Direction::Output),
                  "The output workspace containing the reconstructed data.");
}

//----------------------------------------------------------------------------------------------
/** Validate the input properties.
*/
std::map<std::string, std::string> MaxEnt::validateInputs() {

  std::map<std::string, std::string> result;

  MatrixWorkspace_sptr inWS = getProperty("InputWorkspace");

  if (inWS) {
    // If the input signal is complex, we expect an even number of histograms
    // in the input workspace

    size_t nhistograms = inWS->getNumberHistograms();
    bool complex = getProperty("ComplexData");
    if (complex && (nhistograms % 2))
      result["InputWorkspace"] = "The number of histograms in the input "
                                 "workspace must be even for complex data";
  }

  return result;
}

//----------------------------------------------------------------------------------------------
/** Execute the algorithm.
 */
void MaxEnt::exec() {

  // MaxEnt parameters
  // Complex data?
  const bool complexData = getProperty("ComplexData");
  // Complex image?
  const bool complexImage = getProperty("ComplexImage");
  // Image must be positive?
  const bool positiveImage = getProperty("PositiveImage");
  // Autoshift
  const bool autoShift = getProperty("AutoShift");
  // Increase the number of points in the image by this factor
  const size_t resolutionFactor = getProperty("ResolutionFactor");
  // Background (default level, sky background, etc)
  const double background = getProperty("A");
  // Chi target
  const double chiTarget = getProperty("ChiTarget");
  // Required precision for Chi arget
  const double chiEps = getProperty("ChiEps");
  // Maximum degree of non-parallelism between S and C
  const double angle = getProperty("MaxAngle");
  // Distance penalty for current image
  const double distEps = getProperty("DistancePenalty");
  // Maximum number of iterations
  const size_t niter = getProperty("MaxIterations");
  // Maximum number of iterations in alpha chop
  const size_t alphaIter = getProperty("AlphaChopIterations");
  // Number of spectra and datapoints
  // Read input workspace
  MatrixWorkspace_const_sptr inWS = getProperty("InputWorkspace");
  // Number of spectra
  size_t nspec = inWS->getNumberHistograms();
  // Number of data points - assumed to be constant between spectra or
  // this will throw an exception
  size_t npoints = inWS->blocksize() * resolutionFactor;
  // Number of X bins
  const size_t npointsX = inWS->isHistogramData() ? npoints + 1 : npoints;

  // Is our data space real or complex?
  MaxentSpace_sptr dataSpace;
  if (complexData) {
    dataSpace = std::make_shared<MaxentSpaceComplex>();
  } else {
    dataSpace = std::make_shared<MaxentSpaceReal>();
  }
  // Is our image space real or complex?
  MaxentSpace_sptr imageSpace;
  if (complexImage) {
    imageSpace = std::make_shared<MaxentSpaceComplex>();
  } else {
    imageSpace = std::make_shared<MaxentSpaceReal>();
  }
  // The type of transform. Currently a 1D Fourier Transform
  MaxentTransform_sptr transform =
      std::make_shared<MaxentTransformFourier>(dataSpace, imageSpace);

  // The type of entropy we are going to use (depends on the type of image,
  // positive only, or positive and/or negative)
  MaxentEntropy_sptr entropy;
  if (positiveImage) {
    entropy = std::make_shared<MaxentEntropyPositiveValues>();
  } else {
    entropy = std::make_shared<MaxentEntropyNegativeValues>();
  }

  // Entropy and transform is all we need to set up a calculator
  MaxentCalculator maxentCalculator = MaxentCalculator(entropy, transform);

  // Output workspaces
  MatrixWorkspace_sptr outImageWS;
  MatrixWorkspace_sptr outDataWS;
  MatrixWorkspace_sptr outEvolChi;
  MatrixWorkspace_sptr outEvolTest;

  nspec = complexData ? nspec / 2 : nspec;
  outImageWS =
      WorkspaceFactory::Instance().create(inWS, 2 * nspec, npoints, npoints);
  outDataWS =
      WorkspaceFactory::Instance().create(inWS, 2 * nspec, npointsX, npoints);
  outEvolChi = WorkspaceFactory::Instance().create(inWS, nspec, niter, niter);
  outEvolTest = WorkspaceFactory::Instance().create(inWS, nspec, niter, niter);

  npoints = complexImage ? npoints * 2 : npoints;

  outEvolChi->setPoints(0, Points(niter, LinearGenerator(0.0, 1.0)));
  for (size_t s = 0; s < nspec; s++) {

    // Start distribution (flat background)
    std::vector<double> image(npoints, background);

    std::vector<double> data;
    std::vector<double> errors;
    if (complexData) {
      data = toComplex(inWS, s, false);  // false -> data
      errors = toComplex(inWS, s, true); // true -> errors
    } else {
      data = inWS->y(s).rawData();
      errors = inWS->e(s).rawData();
    }

    // To record the algorithm's progress
    std::vector<double> evolChi(niter, 0.);
    std::vector<double> evolTest(niter, 0.);

    // Progress
    Progress progress(this, 0.0, 1.0, niter);

    // Run maxent algorithm
    for (size_t it = 0; it < niter; it++) {

      // Iterates one step towards the solution. This means calculating
      // quadratic coefficients, search directions, angle and chi-sq
      maxentCalculator.iterate(data, errors, image, background);

      // Calculate delta to construct new image (SB eq. 25)
      double currChisq = maxentCalculator.getChisq();
      auto coeffs = maxentCalculator.getQuadraticCoefficients();
      auto delta = move(coeffs, chiTarget / currChisq, chiEps, alphaIter);

      // Apply distance penalty (SB eq. 33)
      delta = applyDistancePenalty(delta, coeffs, image, background, distEps);

      // Update image
      auto dirs = maxentCalculator.getSearchDirections();
      image = updateImage(image, delta, dirs);

      // Record the evolution of Chi-square and angle(S,C)
      double currAngle = maxentCalculator.getAngle();
      evolChi[it] = currChisq;
      evolTest[it] = currAngle;

      // Stop condition, solution found
      if ((std::abs(currChisq / chiTarget - 1.) < chiEps) &&
          (currAngle < angle)) {
        break;
      }

      // Check for canceling the algorithm
      if (!(it % 1000)) {
        interruption_point();
      }

      progress.report();

    } // iterations

    // Get calculated data
    auto solData = maxentCalculator.getReconstructedData();
    auto solImage = maxentCalculator.getImage();

    // Populate the output workspaces
    populateDataWS(inWS, s, nspec, solData, complexData, outDataWS);
    populateImageWS(inWS, s, nspec, solImage, complexImage, outImageWS,
                    autoShift);

    // Populate workspaces recording the evolution of Chi and Test
    // X values
    outEvolChi->setSharedX(s, outEvolChi->sharedX(0));
    outEvolTest->setSharedX(s, outEvolChi->sharedX(0));

    // Y values
    outEvolChi->setCounts(s, std::move(evolChi));
    outEvolTest->setCounts(s, std::move(evolTest));
    // No errors

  } // Next spectrum

  setProperty("EvolChi", outEvolChi);
  setProperty("EvolAngle", outEvolTest);
  setProperty("ReconstructedImage", outImageWS);
  setProperty("ReconstructedData", outDataWS);
}

//----------------------------------------------------------------------------------------------

/** Returns a given spectrum as a complex number
* @param inWS :: [input] The input workspace containing all the spectra
* @param spec :: [input] The spectrum of interest
* @param errors :: [input] If true, returns the errors, otherwise returns the
* counts
* @return : Spectrum 'spec' as a complex vector
*/
std::vector<double> MaxEnt::toComplex(API::MatrixWorkspace_const_sptr &inWS,
                                      size_t spec, bool errors) {
  const size_t numBins = inWS->y(0).size();
  std::vector<double> result(numBins * 2);

  if (inWS->getNumberHistograms() % 2)
    throw std::invalid_argument(
        "Cannot convert input workspace to complex data");

  size_t nspec = inWS->getNumberHistograms() / 2;

  if (!errors) {
    for (size_t i = 0; i < numBins; i++) {
      result[2 * i] = inWS->y(spec)[i];
      result[2 * i + 1] = inWS->y(spec + nspec)[i];
    }
  } else {
    for (size_t i = 0; i < numBins; i++) {
      result[2 * i] = inWS->e(spec)[i];
      result[2 * i + 1] = inWS->e(spec + nspec)[i];
    }
  }
  return result;
}

/** Bisection method to move delta one step closer towards the solution
* @param coeffs :: [input] The current quadratic coefficients
* @param chiTarget :: [input] The requested Chi target
* @param chiEps :: [input] Precision required for Chi target
* @param alphaIter :: [input] Maximum number of iterations in the bisection
* method (alpha chop)
* @return : The increment length to be added to the current image
*/
std::vector<double> MaxEnt::move(const QuadraticCoefficients &coeffs,
                                 double chiTarget, double chiEps,
                                 size_t alphaIter) {

  double aMin = 0.; // Minimum alpha
  double aMax = 1.; // Maximum alpha

  // Dimension, number of search directions
  size_t dim = coeffs.c2.size().first;

  std::vector<double> deltaMin(dim, 0); // delta at alpha min
  std::vector<double> deltaMax(dim, 0); // delta at alpha max
  double chiMin = calculateChi(coeffs, aMin, deltaMin); // Chi at alpha min
  double chiMax = calculateChi(coeffs, aMax, deltaMax); // Chi at alpha max

  double dchiMin = chiMin - chiTarget; // max - target
  double dchiMax = chiMax - chiTarget; // min - target

  if (dchiMin * dchiMax > 0) {
    // ChiTarget could be outside the range [chiMin, chiMax]

    if (fabs(dchiMin) < fabs(dchiMax)) {
      return deltaMin;
    } else {
      return deltaMax;
    }
    //    throw std::runtime_error("Error in alpha chop\n");
  }

  // Initial values of eps and iter to start while loop
  double eps = 2. * chiEps;
  size_t iter = 0;

  // Bisection method

  std::vector<double> delta(dim, 0); // delta at current alpha

  while ((fabs(eps) > chiEps) && (iter < alphaIter)) {

    double aMid = 0.5 * (aMin + aMax);
    double chiMid = calculateChi(coeffs, aMid, delta);

    eps = chiMid - chiTarget;

    if (dchiMin * eps > 0) {
      aMin = aMid;
      dchiMin = eps;
    }

    if (dchiMax * eps > 0) {
      aMax = aMid;
      dchiMax = eps;
    }

    iter++;
  }

  // Check if move was successful
  if ((fabs(eps) > chiEps) || (iter > alphaIter)) {

    throw std::runtime_error("Error encountered when calculating solution "
                             "image. No convergence in alpha chop.\n");
  }

  return delta;
}

/** Calculates Chi given the quadratic coefficients and an alpha value by
* solving the matrix equation A*b = B
* @param coeffs :: [input] The quadratic coefficients
* @param a :: [input] The alpha value
* @param b :: [output] The solution
* @return :: The calculated chi-square
*/
double MaxEnt::calculateChi(const QuadraticCoefficients &coeffs, double a,
                            std::vector<double> &b) {

  size_t dim = coeffs.c2.size().first;

  double ax = a;
  double bx = 1 - ax;
  Kernel::DblMatrix A(dim, dim);
  Kernel::DblMatrix B(dim, 1);

  // Construct the matrix A and vector B such that Ax=B
  for (size_t k = 0; k < dim; k++) {
    for (size_t l = 0; l < dim; l++) {
      A[k][l] = bx * coeffs.c2[k][l] - ax * coeffs.s2[k][l];
    }
    B[k][0] = -bx * coeffs.c1[k][0] + ax * coeffs.s1[k][0];
  }

  // Alternatives I have tried:
  // Gauss-Jordan
  // LU
  // SVD seems to work better

  // Solve using SVD
  b = solveSVD(A, B);

  // Now compute Chi
  double ww = 0.;
  for (size_t k = 0; k < dim; k++) {
    double z = 0.;
    for (size_t l = 0; l < dim; l++) {
      z += coeffs.c2[k][l] * b[l];
    }
    ww += b[k] * (coeffs.c1[k][0] + 0.5 * z);
  }

  // Return chi
  return ww + 1.;
}

/** Solves A*x = B using SVD
* @param A :: [input] The matrix A
* @param B :: [input] The vector B
* @return :: The solution x
*/
std::vector<double> MaxEnt::solveSVD(const DblMatrix &A, const DblMatrix &B) {

  size_t dim = A.size().first;

  gsl_matrix *a = gsl_matrix_alloc(dim, dim);
  gsl_matrix *v = gsl_matrix_alloc(dim, dim);
  gsl_vector *s = gsl_vector_alloc(dim);
  gsl_vector *w = gsl_vector_alloc(dim);
  gsl_vector *x = gsl_vector_alloc(dim);
  gsl_vector *b = gsl_vector_alloc(dim);

  // Need to copy from DblMatrix to gsl matrix

  for (size_t k = 0; k < dim; k++)
    for (size_t l = 0; l < dim; l++)
      gsl_matrix_set(a, k, l, A[k][l]);
  for (size_t k = 0; k < dim; k++)
    gsl_vector_set(b, k, B[k][0]);

  // Singular value decomposition
  gsl_linalg_SV_decomp(a, v, s, w);

  // A could be singular or ill-conditioned. We can use SVD to obtain a least
  // squares
  // solution by setting the small (compared to the maximum) singular values to
  // zero

  // Find largest sing value
  double max = gsl_vector_get(s, 0);
  for (size_t i = 0; i < dim; i++) {
    if (max < gsl_vector_get(s, i))
      max = gsl_vector_get(s, i);
  }

  // Apply a threshold to small singular values
  const double THRESHOLD = 1E-6;
  double threshold = THRESHOLD * max;

  for (size_t i = 0; i < dim; i++)
    if (gsl_vector_get(s, i) > threshold)
      gsl_vector_set(s, i, gsl_vector_get(s, i));
    else
      gsl_vector_set(s, i, 0);

  // Solve A*x = B
  gsl_linalg_SV_solve(a, v, s, b, x);

  // From gsl_vector to vector
  std::vector<double> delta(dim);
  for (size_t k = 0; k < dim; k++)
    delta[k] = gsl_vector_get(x, k);

  gsl_matrix_free(a);
  gsl_matrix_free(v);
  gsl_vector_free(s);
  gsl_vector_free(w);
  gsl_vector_free(x);
  gsl_vector_free(b);

  return delta;
}

/** Applies a distance penalty
* @param delta :: [input] The current increment
* @param coeffs :: [input] The quadratic coefficients
* @param image :: [input] The current image
* @param background :: [input] The background
* @param distEps :: [input] The distance constraint
* @return :: The new increment
*/
std::vector<double> MaxEnt::applyDistancePenalty(
    const std::vector<double> &delta, const QuadraticCoefficients &coeffs,
    const std::vector<double> &image, double background, double distEps) {

  double sum = 0.;
  for (double point : image)
    sum += fabs(point);

  size_t dim = coeffs.s2.size().first;

  double dist = 0.;

  for (size_t k = 0; k < dim; k++) {
    double sum = 0.0;
    for (size_t l = 0; l < dim; l++)
      sum -= coeffs.s2[k][l] * delta[l];
    dist += delta[k] * sum;
  }

  auto newDelta = delta;
  if (dist > distEps * sum / background) {
    for (size_t k = 0; k < delta.size(); k++) {
      newDelta[k] *= sqrt(sum / dist / background);
    }
  }
  return newDelta;
}

/**
* Updates the image according to an increment delta
* @param image : [input] The current image as a vector (can be real or complex)
* @param delta : [input] The increment delta as a vector (can be real or
* complex)
* @param dirs : [input] The search directions
* @return : The new image
*/
std::vector<double>
MaxEnt::updateImage(const std::vector<double> &image,
                    const std::vector<double> &delta,
                    const std::vector<std::vector<double>> dirs) {

  std::vector<double> newImage = image;

  if (image.empty() || dirs.empty() || (delta.size() != dirs.size())) {
    throw std::runtime_error("Cannot calculate new image");
  }

  // Calculate the new image
  for (size_t i = 0; i < image.size(); i++) {
    for (size_t k = 0; k < delta.size(); k++) {
      newImage[i] = newImage[i] + delta[k] * dirs[k][i];
    }
  }
  return newImage;
}

/** Populates the output workspaces
* @param inWS :: [input] The input workspace
* @param spec :: [input] The current spectrum being analyzed
* @param nspec :: [input] The total number of histograms in the input workspace
* @param result :: [input] The image to be written in the output workspace (can
* be real or complex vector)
* @param complex :: [input] True if the result is a complex vector, false
* otherwise
* @param outWS :: [input] The output workspace to populate
* @param autoShift :: [input] Whether or not to correct the phase shift
*/
void MaxEnt::populateImageWS(MatrixWorkspace_const_sptr &inWS, size_t spec,
                             size_t nspec, const std::vector<double> &result,
                             bool complex, MatrixWorkspace_sptr &outWS,
                             bool autoShift) {

  if (complex && result.size() % 2)
    throw std::invalid_argument("Cannot write results to output workspaces");

  int npoints = complex ? static_cast<int>(result.size() / 2)
                        : static_cast<int>(result.size());
  MantidVec X(npoints);
  MantidVec YR(npoints);
  MantidVec YI(npoints);
  MantidVec E(npoints, 0.);

  auto dataPoints = inWS->points(spec);
  double x0 = dataPoints[0];
  double dx = dataPoints[1] - x0;

  double delta = 1. / dx / npoints;
  const int isOdd = (inWS->y(0).size() % 2) ? 1 : 0;

  double shift = x0 * 2. * M_PI;
  if (!autoShift)
    shift = 0.;

  // X values
  for (int i = 0; i < npoints; i++) {
    X[i] = delta * (-npoints / 2 + i);
  }

  // Y values
  if (complex) {
    for (int i = 0; i < npoints; i++) {
      int j = (npoints / 2 + i + isOdd) % npoints;
      double xShift = X[i] * shift;
      double c = cos(xShift);
      double s = sin(xShift);
      YR[i] = result[2 * j] * c - result[2 * j + 1] * s;
      YI[i] = result[2 * j] * s + result[2 * j + 1] * c;
      YR[i] *= dx;
      YI[i] *= dx;
    }
  } else {
    for (int i = 0; i < npoints; i++) {
      int j = (npoints / 2 + i + isOdd) % npoints;
      double xShift = X[i] * shift;
      double c = cos(xShift);
      double s = sin(xShift);
      YR[i] = result[j] * c;
      YI[i] = result[j] * s;
      YR[i] *= dx;
      YI[i] *= dx;
    }
  }

  // X caption & label
  auto inputUnit = inWS->getAxis(0)->unit();
  if (inputUnit) {
    boost::shared_ptr<Kernel::Units::Label> lblUnit =
        boost::dynamic_pointer_cast<Kernel::Units::Label>(
            UnitFactory::Instance().create("Label"));
    if (lblUnit) {

      lblUnit->setLabel(
          inverseCaption[inWS->getAxis(0)->unit()->caption()],
          inverseLabel[inWS->getAxis(0)->unit()->label().ascii()]);
      outWS->getAxis(0)->unit() = lblUnit;
    }
  }

  outWS->mutableX(spec) = std::move(X);
  outWS->mutableY(spec) = std::move(YR);
  outWS->mutableE(spec) = std::move(E);
  outWS->setSharedX(nspec + spec, outWS->sharedX(spec));
  outWS->mutableY(nspec + spec) = std::move(YI);
  outWS->setSharedE(nspec + spec, outWS->sharedE(spec));
}

/** Populates the output workspaces
* @param inWS :: [input] The input workspace
* @param spec :: [input] The current spectrum being analyzed
* @param nspec :: [input] The total number of histograms in the input workspace
* @param result :: [input] The reconstructed data to be written in the output
* workspace (can be a real or complex vector)
* @param complex :: [input] True if result is a complex vector, false otherwise
* @param outWS :: [input] The output workspace to populate
*/
void MaxEnt::populateDataWS(MatrixWorkspace_const_sptr &inWS, size_t spec,
                            size_t nspec, const std::vector<double> &result,
                            bool complex, MatrixWorkspace_sptr &outWS) {

  if (complex && result.size() % 2)
    throw std::invalid_argument("Cannot write results to output workspaces");

  int npoints = complex ? static_cast<int>(result.size() / 2)
                        : static_cast<int>(result.size());
  int npointsX = inWS->isHistogramData() ? npoints + 1 : npoints;
  MantidVec X(npointsX);
  MantidVec YR(npoints);
  MantidVec YI(npoints);
  MantidVec E(npoints, 0.);

  double x0 = inWS->x(spec)[0];
  double dx = inWS->x(spec)[1] - x0;

  // X values
  for (int i = 0; i < npointsX; i++) {
    X[i] = x0 + i * dx;
  }

  // Y values
  if (complex) {
    for (int i = 0; i < npoints; i++) {
      YR[i] = result[2 * i];
      YI[i] = result[2 * i + 1];
    }
  } else {
    for (int i = 0; i < npoints; i++) {
      YR[i] = result[i];
      YI[i] = 0.;
    }
  }

  outWS->mutableX(spec) = std::move(X);
  outWS->mutableY(spec) = std::move(YR);
  outWS->mutableE(spec) = std::move(E);
  outWS->setSharedX(nspec + spec, outWS->sharedX(spec));
  outWS->mutableY(nspec + spec) = std::move(YI);
  outWS->setSharedE(nspec + spec, outWS->sharedE(spec));
}

} // namespace Algorithms
} // namespace Mantid