-
Federico Montesino Pouzols authoredFederico Montesino Pouzols authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
FFT.cpp 11.16 KiB
//----------------------------------------------------------------------
// Includes
//----------------------------------------------------------------------
#include "MantidAlgorithms/FFT.h"
#include "MantidKernel/UnitFactory.h"
#include "MantidKernel/UnitLabelTypes.h"
#include "MantidAPI/TextAxis.h"
#include <boost/shared_array.hpp>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_fft_complex.h>
#define REAL(z, i) ((z)[2 * (i)])
#define IMAG(z, i) ((z)[2 * (i) + 1])
#include <sstream>
#include <numeric>
#include <algorithm>
#include <functional>
#include <cmath>
#include "MantidKernel/BoundedValidator.h"
#include "MantidKernel/ListValidator.h"
namespace Mantid {
namespace Algorithms {
// Register the class into the algorithm factory
DECLARE_ALGORITHM(FFT)
using namespace Kernel;
using namespace API;
/// Initialisation method. Declares properties to be used in algorithm.
void FFT::init() {
declareProperty(
new WorkspaceProperty<>("InputWorkspace", "", Direction::Input),
"The name of the input workspace.");
declareProperty(
new WorkspaceProperty<>("OutputWorkspace", "", Direction::Output),
"The name of the output workspace.");
// if desired, provide the imaginary part in a separate workspace.
declareProperty(new WorkspaceProperty<>("InputImagWorkspace", "",
Direction::Input,
PropertyMode::Optional),
"The name of the input workspace for the imaginary part. "
"Leave blank if same as InputWorkspace");
auto mustBePositive = boost::make_shared<BoundedValidator<int>>();
mustBePositive->setLower(0);
declareProperty("Real", 0, mustBePositive,
"Spectrum number to use as real part for transform");
declareProperty("Imaginary", EMPTY_INT(), mustBePositive,
"Spectrum number to use as imaginary part for transform");
std::vector<std::string> fft_dir;
fft_dir.push_back("Forward");
fft_dir.push_back("Backward");
declareProperty("Transform", "Forward",
boost::make_shared<StringListValidator>(fft_dir),
"Direction of the transform: forward or backward");
declareProperty("Shift", 0.0, "Apply an extra phase equal to this quantity "
"times 2*pi to the transform");
}
/** Executes the algorithm
*
* @throw std::invalid_argument if the input properties are invalid
or the bins of the spectrum being transformed
do not have constant width
*/
void FFT::exec() {
MatrixWorkspace_const_sptr inWS = getProperty("InputWorkspace");
MatrixWorkspace_const_sptr inImagWS = getProperty("InputImagWorkspace");
if (!inImagWS)
inImagWS = inWS; // workspaces are one and the same
const int iReal = getProperty("Real");
const int iImag = getProperty("Imaginary");
const bool isComplex = iImag != EMPTY_INT();
const MantidVec &X = inWS->readX(iReal);
const int ySize = static_cast<int>(inWS->blocksize());
const int xSize = static_cast<int>(X.size());
int nHist = static_cast<int>(inWS->getNumberHistograms());
if (iReal >= nHist)
throw std::invalid_argument("Property Real is out of range");
if (isComplex) {
const int yImagSize = static_cast<int>(inImagWS->blocksize());
if (ySize != yImagSize)
throw std::length_error("Real and Imaginary sizes do not match");
nHist = static_cast<int>(inImagWS->getNumberHistograms());
if (iImag >= nHist)
throw std::invalid_argument("Property Imaginary is out of range");
}
// check that the workspace isn't empty
if (X.size() < 2) {
throw std::invalid_argument(
"Input workspace must have at least two values");
}
// Check that the x values are evenly spaced
const double dx = X[1] - X[0];
for (size_t i = 1; i < X.size() - 2; i++)
if (std::abs(dx - X[i + 1] + X[i]) / dx > 1e-7) {
g_log.error() << "dx=" << X[i + 1] - X[i] << ' ' << dx << ' ' << i
<< std::endl;
throw std::invalid_argument(
"X axis must be linear (all bins have same width)");
}
gsl_fft_complex_wavetable *wavetable = gsl_fft_complex_wavetable_alloc(ySize);
gsl_fft_complex_workspace *workspace = gsl_fft_complex_workspace_alloc(ySize);
boost::shared_array<double> data(new double[2 * ySize]);
const std::string transform = getProperty("Transform");
// The number of spectra in the output workspace
int nOut = 3;
bool addPositiveOnly = false;
// If the input is real add 3 more spectra with positive "frequencies" only
if (!isComplex && transform == "Forward") {
nOut += 3;
addPositiveOnly = true;
}
MatrixWorkspace_sptr outWS =
WorkspaceFactory::Instance().create(inWS, nOut, xSize, ySize);
double df = 1.0 / (dx * ySize);
// Output label
outWS->getAxis(0)->unit() = UnitFactory::Instance().create("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) {
if ((inputUnit->caption() == "Energy" ||
inputUnit->caption() == "Energy transfer") &&
inputUnit->label() == "meV") {
lblUnit->setLabel("Time", "ns");
df /= 2.418e2;
} else if (inputUnit->caption() == "Time" && inputUnit->label() == "s") {
lblUnit->setLabel("Frequency", "Hz");
} else if (inputUnit->caption() == "Frequency" &&
inputUnit->label() == "Hz") {
lblUnit->setLabel("Time", "s");
} else if (inputUnit->caption() == "Time" &&
inputUnit->label() == "microsecond") {
lblUnit->setLabel("Frequency", "MHz");
} else if (inputUnit->caption() == "Frequency" &&
inputUnit->label() == "MHz") {
lblUnit->setLabel("Time", Units::Symbol::Microsecond);
} else if (inputUnit->caption() == "d-Spacing" &&
inputUnit->label() == "Angstrom") {
lblUnit->setLabel("q", Units::Symbol::InverseAngstrom);
} else if (inputUnit->caption() == "q" &&
inputUnit->label() == "Angstrom^-1") {
lblUnit->setLabel("d-Spacing", Units::Symbol::Angstrom);
}
outWS->getAxis(0)->unit() = lblUnit;
}
}
// centerShift == true means that the zero on the x axis is assumed to be in
// the data centre
// at point with index i = ySize/2. If shift == false the zero is at i = 0
const bool centerShift = true;
API::TextAxis *tAxis = new API::TextAxis(nOut);
int iRe = 0;
int iIm = 1;
int iAbs = 2;
if (addPositiveOnly) {
iRe = 3;
iIm = 4;
iAbs = 5;
tAxis->setLabel(0, "Real Positive");
tAxis->setLabel(1, "Imag Positive");
tAxis->setLabel(2, "Modulus Positive");
}
tAxis->setLabel(iRe, "Real");
tAxis->setLabel(iIm, "Imag");
tAxis->setLabel(iAbs, "Modulus");
outWS->replaceAxis(1, tAxis);
const int dys = ySize % 2;
if (transform == "Forward") {
/* If we translate the X-axis by -dx*ySize/2 and assume that our function is
* periodic
* along the X-axis with period equal to ySize, then dataY values must be
* rearranged such that
* dataY[i] = dataY[(ySize/2 + i + dys) % ySize]. However, we do not
* overwrite dataY but
* store the rearranged values in array 'data'.
* When index 'i' runs from 0 to ySize/2, data[2*i] will store dataY[j] with
* j running from
* ySize/2 to ySize. When index 'i' runs from ySize/2+1 to ySize, data[2*i]
* will store
* dataY[j] with j running from 0 to ySize.
*/
for (int i = 0; i < ySize; i++) {
int j = centerShift ? (ySize / 2 + i) % ySize : i;
data[2 * i] =
inWS->dataY(iReal)[j]; // even indexes filled with the real part
data[2 * i + 1] = isComplex
? inImagWS->dataY(iImag)[j]
: 0.; // odd indexes filled with the imaginary part
}
double shift =
getProperty("Shift"); // extra phase to be applied to the transform
shift *= 2 * M_PI;
gsl_fft_complex_forward(data.get(), 1, ySize, wavetable, workspace);
/* The Fourier transform overwrites array 'data'. Recall that the Fourier
* transform is
* periodic along the frequency axis. Thus, 'data' takes the same values
* when index j runs
* from ySize/2 to ySize than if index j would run from -ySize/2 to 0. Thus,
* for negative
* frequencies running from -ySize/2 to 0, we use the values stored in array
* 'data'
* for index j running from ySize/2 to ySize.
*/
for (int i = 0; i < ySize; i++) {
int j = (ySize / 2 + i + dys) % ySize;
outWS->dataX(iRe)[i] =
df * (-ySize / 2 + i); // zero frequency at i = ySize/2
double re = data[2 * j] *
dx; // use j from ySize/2 to ySize for negative frequencies
double im = data[2 * j + 1] * dx;
// shift
{
double c = cos(outWS->dataX(iRe)[i] * shift);
double s = sin(outWS->dataX(iRe)[i] * shift);
double re1 = re * c - im * s;
double im1 = re * s + im * c;
re = re1;
im = im1;
}
outWS->dataY(iRe)[i] = re; // real part
outWS->dataY(iIm)[i] = im; // imaginary part
outWS->dataY(iAbs)[i] = sqrt(re * re + im * im); // modulus
if (addPositiveOnly) {
outWS->dataX(0)[i] = df * i;
if (j < ySize / 2) {
outWS->dataY(0)[j] = re; // real part
outWS->dataY(1)[j] = im; // imaginary part
outWS->dataY(2)[j] = sqrt(re * re + im * im); // modulus
} else {
outWS->dataY(0)[j] = 0.; // real part
outWS->dataY(1)[j] = 0.; // imaginary part
outWS->dataY(2)[j] = 0.; // modulus
}
}
}
if (xSize == ySize + 1) {
outWS->dataX(0)[ySize] = outWS->dataX(0)[ySize - 1] + df;
if (addPositiveOnly)
outWS->dataX(iRe)[ySize] = outWS->dataX(iRe)[ySize - 1] + df;
}
} else // Backward
{
for (int i = 0; i < ySize; i++) {
int j = (ySize / 2 + i) % ySize;
data[2 * i] = inWS->dataY(iReal)[j];
data[2 * i + 1] = isComplex ? inImagWS->dataY(iImag)[j] : 0.;
}
gsl_fft_complex_inverse(data.get(), 1, ySize, wavetable, workspace);
for (int i = 0; i < ySize; i++) {
double x = df * i;
if (centerShift)
x -= df * (ySize / 2);
outWS->dataX(0)[i] = x;
int j = centerShift ? (ySize / 2 + i + dys) % ySize : i;
double re = data[2 * j] / df;
double im = data[2 * j + 1] / df;
outWS->dataY(0)[i] = re; // real part
outWS->dataY(1)[i] = im; // imaginary part
outWS->dataY(2)[i] = sqrt(re * re + im * im); // modulus
}
if (xSize == ySize + 1)
outWS->dataX(0)[ySize] = outWS->dataX(0)[ySize - 1] + df;
}
gsl_fft_complex_wavetable_free(wavetable);
gsl_fft_complex_workspace_free(workspace);
outWS->dataX(1) = outWS->dataX(0);
outWS->dataX(2) = outWS->dataX(0);
if (addPositiveOnly) {
outWS->dataX(iIm) = outWS->dataX(iRe);
outWS->dataX(iAbs) = outWS->dataX(iRe);
}
setProperty("OutputWorkspace", outWS);
}
} // namespace Algorithm
} // namespace Mantid