Newer
Older
#include "MantidAlgorithms/MonteCarloAbsorption.h"
#include "MantidAlgorithms/InterpolationOption.h"
#include "MantidAPI/AnalysisDataService.h"
#include "MantidAPI/ExperimentInfo.h"
#include "MantidAPI/InstrumentValidator.h"
#include "MantidAPI/Run.h"
#include "MantidAPI/Sample.h"
#include "MantidAPI/SpectrumInfo.h"
Federico Montesino Pouzols
committed
#include "MantidAPI/WorkspaceFactory.h"
#include "MantidAPI/WorkspaceProperty.h"
#include "MantidAPI/WorkspaceUnitValidator.h"
#include "MantidAlgorithms/SampleCorrections/MCAbsorptionStrategy.h"
#include "MantidAlgorithms/SampleCorrections/RectangularBeamProfile.h"
#include "MantidDataObjects/Workspace2D.h"
#include "MantidDataObjects/WorkspaceCreation.h"
Federico Montesino Pouzols
committed
#include "MantidGeometry/Instrument.h"
#include "MantidGeometry/Instrument/Detector.h"
#include "MantidGeometry/Instrument/ReferenceFrame.h"
#include "MantidGeometry/Instrument/SampleEnvironment.h"
#include "MantidGeometry/Objects/ShapeFactory.h"
#include "MantidHistogramData/Histogram.h"
#include "MantidKernel/BoundedValidator.h"
#include "MantidKernel/CompositeValidator.h"
#include "MantidKernel/DeltaEMode.h"
#include "MantidKernel/MersenneTwister.h"
#include "MantidKernel/PhysicalConstants.h"
#include "MantidKernel/VectorHelper.h"
#include "MantidHistogramData/HistogramX.h"
#include "MantidHistogramData/Interpolate.h"
#include <Poco/DOM/AutoPtr.h>
#include <Poco/DOM/Document.h>
using namespace Mantid::API;
using namespace Mantid::Geometry;
using namespace Mantid::Kernel;
using Mantid::HistogramData::Histogram;
using Mantid::HistogramData::HistogramX;
using Mantid::HistogramData::Points;
using Mantid::HistogramData::interpolateLinearInplace;
using Mantid::DataObjects::Workspace2D;
namespace PhysicalConstants = Mantid::PhysicalConstants;
constexpr int DEFAULT_NEVENTS = 300;
constexpr int DEFAULT_SEED = 123456789;
constexpr int DEFAULT_LATITUDINAL_DETS = 4;
constexpr int DEFAULT_LONGITUDINAL_DETS = 10;
/// Energy (meV) to wavelength (angstroms)
inline double toWavelength(double energy) {
static const double factor =
1e10 * PhysicalConstants::h /
sqrt(2.0 * PhysicalConstants::NeutronMass * PhysicalConstants::meV);
return factor / sqrt(energy);
}
struct EFixedProvider {
explicit EFixedProvider(const ExperimentInfo &expt)
: m_expt(expt), m_emode(expt.getEMode()), m_value(0.0) {
if (m_emode == DeltaEMode::Direct) {
m_value = m_expt.getEFixed();
}
}
inline DeltaEMode::Type emode() const { return m_emode; }
inline double value(const Mantid::detid_t detID) const {
if (m_emode != DeltaEMode::Indirect)
return m_value;
else
return m_expt.getEFixed(detID);
private:
const ExperimentInfo &m_expt;
const DeltaEMode::Type m_emode;
double m_value;
};
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
std::pair<double, double> geographicalAngles(const V3D &p) {
const double lat = std::atan2(p.Y(), std::hypot(p.X() , p.Z()));
const double lon = std::atan2(p.X(), p.Z());
return std::pair<double, double>(lat, lon);
}
std::tuple<double, double, double, double> extremeAngles(const MatrixWorkspace &ws) {
const auto &spectrumInfo = ws.spectrumInfo();
double minLat = std::numeric_limits<double>::max();
double maxLat = std::numeric_limits<double>::lowest();
double minLong = std::numeric_limits<double>::max();
double maxLong = std::numeric_limits<double>::lowest();
for (size_t i = 0; i < ws.getNumberHistograms(); ++i) {
double lat, lon;
std::tie(lat, lon) = geographicalAngles(spectrumInfo.position(i));
if (lat < minLat) {
minLat = lat;
} else if (lat > maxLat) {
maxLat = lat;
}
if (lon < minLong) {
minLong = lon;
} else if (lon > maxLong) {
maxLong = lon;
}
}
return std::tie(minLat, maxLat, minLong, maxLong);
}
std::tuple<std::set<double>, std::set<double>> latitudeLongitudeGrid(const double minLat, const double maxLat, const size_t latPoints, const double minLong, const double maxLong, const size_t longPoints) {
const double latStep = (maxLat - minLat) / static_cast<double>(latPoints - 1);
std::set<double> lats;
for (size_t i = 0; i < latPoints; ++i) {
lats.emplace_hint(lats.cend(), minLat + static_cast<double>(i) * latStep);
}
const double longStep = (maxLong - minLong) / static_cast<double>(longPoints - 1);
std::set<double> longs;
for (size_t i = 0; i < longPoints; ++i) {
longs.emplace_hint(longs.cend(), minLong + static_cast<double>(i) * longStep);
}
return std::tie(lats, longs);
}
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
Object_sptr makeCubeShape() {
using namespace Poco::XML;
const double dimension = 0.02;
AutoPtr<Document> shapeDescription = new Document;
AutoPtr<Element> typeElement = shapeDescription->createElement("type");
typeElement->setAttribute("name", "detector");
AutoPtr<Element> shapeElement = shapeDescription->createElement("cuboid");
shapeElement->setAttribute("id", "cube");
const std::string posCoord = std::to_string(dimension / 2);
const std::string negCoord = std::to_string(-dimension / 2);
AutoPtr<Element> element =
shapeDescription->createElement("left-front-bottom-point");
element->setAttribute("x", negCoord);
element->setAttribute("y", negCoord);
element->setAttribute("z", posCoord);
shapeElement->appendChild(element);
element = shapeDescription->createElement("left-front-top-point");
element->setAttribute("x", negCoord);
element->setAttribute("y", posCoord);
element->setAttribute("z", posCoord);
shapeElement->appendChild(element);
element = shapeDescription->createElement("left-back-bottom-point");
element->setAttribute("x", negCoord);
element->setAttribute("y", negCoord);
element->setAttribute("z", negCoord);
shapeElement->appendChild(element);
element = shapeDescription->createElement("right-front-bottom-point");
element->setAttribute("x", posCoord);
element->setAttribute("y", negCoord);
element->setAttribute("z", posCoord);
shapeElement->appendChild(element);
typeElement->appendChild(shapeElement);
AutoPtr<Element> algebraElement =
shapeDescription->createElement("algebra");
algebraElement->setAttribute("val", "cube");
typeElement->appendChild(algebraElement);
ShapeFactory shapeFactory;
return shapeFactory.createShape(typeElement);
}
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
MatrixWorkspace_uptr createWSWithSimulationInstrument(const MatrixWorkspace &modelWS, const std::set<double> &lats, const std::set<double> &longs) {
auto instrument = boost::make_shared<Instrument>("MC_simulation_instrument");
instrument->setReferenceFrame(
boost::make_shared<ReferenceFrame>(Y, Z, Right, ""));
const V3D samplePos{0.0, 0.0, 0.0};
auto sample = Mantid::Kernel::make_unique<ObjComponent>("sample", nullptr, instrument.get());
sample->setPos(samplePos);
instrument->add(sample.get());
instrument->markAsSamplePos(sample.release());
const double R = 1.0;
const V3D sourcePos{0.0, 0.0, -2.0 * R};
auto source = Mantid::Kernel::make_unique<ObjComponent>("source", nullptr, instrument.get());
source->setPos(sourcePos);
instrument->add(source.get());
instrument->markAsSource(source.release());
const size_t numSpectra = lats.size() * longs.size();
auto ws = Mantid::DataObjects::create<Workspace2D>(numSpectra, modelWS.histogram(0));
auto detShape = makeCubeShape();
size_t index = 0;
for (const auto lat : lats) {
for (const auto lon : longs) {
const int detID = static_cast<int>(index);
std::ostringstream detName;
detName << "det-" << detID;
auto det = Mantid::Kernel::make_unique<Detector>(detName.str(), detID, detShape, instrument.get());
const double x = R * std::sin(lon) * std::cos(lat);
const double y = R * std::sin(lat);
const double z = R * std::cos(lon) * std::cos(lat);
det->setPos(x, y, z);
ws->getSpectrum(index).setDetectorID(detID);
instrument->add(det.get());
instrument->markAsDetector(det.release());
++index;
}
}
ws->setInstrument(instrument);
auto ¶mMap = ws->instrumentParameters();
auto parametrizedInstrument = ws->getInstrument();
const auto modelSource = modelWS.getInstrument()->getSource();
const auto beamWidthParam = modelSource->getNumberParameter("beam-width");
const auto beamHeightParam = modelSource->getNumberParameter("beam-height");
if (beamWidthParam.size() == 1 && beamHeightParam.size() == 1) {
auto parametrizedSource = parametrizedInstrument->getSource();
paramMap.add("double", parametrizedSource.get(), "beam-width", beamWidthParam[0]);
paramMap.add("double", parametrizedSource.get(), "beam-height", beamHeightParam[0]);
}
EFixedProvider eFixed(modelWS);
ws->mutableRun().addProperty("deltaE-mode", Mantid::Kernel::DeltaEMode::asString(eFixed.emode()));
if (eFixed.emode() == Mantid::Kernel::DeltaEMode::Direct) {
ws->mutableRun().addProperty("Ei", eFixed.value(0));
} else if (eFixed.emode() == Mantid::Kernel::DeltaEMode::Indirect) {
throw std::runtime_error("Sparse instrument with indirect mode not yet supported.");
}
return MatrixWorkspace_uptr(ws.release());
}
struct SparseInstrumentOption {
bool use;
int latitudinalDets = DEFAULT_LATITUDINAL_DETS;
int longitudinalDets = DEFAULT_LONGITUDINAL_DETS;
SparseInstrumentOption(const bool use_) : use(use_) {}
MatrixWorkspace_uptr chooseSimulationWS(MatrixWorkspace_uptr nonSparse) const;
const MatrixWorkspace &chooseInstrumentWS(const MatrixWorkspace_uptr &sparse, const MatrixWorkspace &nonSparse) const;
};
MatrixWorkspace_uptr SparseInstrumentOption::chooseSimulationWS(MatrixWorkspace_uptr nonSparse) const {
if (!use) {
return nonSparse;
}
double minLat, maxLat, minLong, maxLong;
std::tie(minLat, maxLat, minLong, maxLong) = extremeAngles(*nonSparse);
std::set<double> lats, longs;
std::tie(lats, longs) = latitudeLongitudeGrid(minLat, maxLat, latitudinalDets, minLong, maxLong, longitudinalDets);
return createWSWithSimulationInstrument(*nonSparse, lats, longs);
}
const MatrixWorkspace &SparseInstrumentOption::chooseInstrumentWS(const MatrixWorkspace_uptr &sparse, const MatrixWorkspace &nonSparse) const {
return use ? *sparse : nonSparse;
}
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
std::array<size_t, 4> nearestNeighbourIndices(const double lat, const double lon, const std::set<double> &lats, const std::set<double> &longs) {
if (lats.upper_bound(lat) == lats.cbegin() || lats.upper_bound(lat) == lats.cend() || longs.upper_bound(lon) == longs.cbegin() || longs.upper_bound(lon) == longs.cend()) {
throw std::runtime_error("BUG: Simulation instrument was to small to accommodate all detectors.");
}
const auto lowerLatBound = std::lower_bound(lats.cbegin(), lats.cend(), lat);
auto latIndex = static_cast<size_t>(std::distance(lats.cbegin(), lowerLatBound));
if (latIndex == 0) {
latIndex = 1;
} else if (latIndex == lats.size()) {
--latIndex;
}
const auto lowerLongBound = std::lower_bound(longs.cbegin(), longs.cend(), lon);
auto longIndex = static_cast<size_t>(std::distance(longs.cbegin(), lowerLongBound));
if (longIndex == 0) {
longIndex = 1;
} else if (longIndex == longs.size()) {
--longIndex;
}
std::array<size_t, 4> ids;
ids[0] = (latIndex - 1) * lats.size() + longIndex - 1;
ids[1] = ids[0] + 1;
ids[2] = ids[0] + lats.size();
ids[3] = ids[2] + 1;
return ids;
}
double greatCircleDistance(const double lat1, const double long1, const double lat2, const double long2) {
const double latD = std::sin((lat2 - lat1) / 2.0);
const double longD = std::sin((long2 - long1) / 2.0);
const double S = latD * latD + std::cos(lat1) * std::cos(lat2) * longD * longD;
return 2.0 * std::asin(std::sqrt(S));
}
std::array<double, 4> inverseDistanceWeights(const std::array<double, 4> &distances) {
std::array<double, 4> weights;
for (size_t i = 0; i < weights.size(); ++i) {
if (distances[i] == 0.0) {
weights.fill(0.0);
weights[i] = 1.0;
return weights;
}
weights[i] = 1.0 / distances[i] / distances[i];
}
return weights;
}
Mantid::HistogramData::HistogramY interpolateY(const double lat, const double lon, const MatrixWorkspace &ws, const std::array<size_t, 4> &indices) {
const auto &spectrumInfo = ws.spectrumInfo();
std::array<double, 4> distances;
for (size_t i = 0; i < 4; ++i) {
double detLat, detLong;
std::tie(detLat, detLong) = geographicalAngles(spectrumInfo.position(indices[i]));
distances[i] = greatCircleDistance(lat, lon, detLat, detLong);
}
const auto weights = inverseDistanceWeights(distances);
auto weightSum = weights[0];
auto ys = weights[0] * ws.y(indices[0]);
for (size_t i = 1; i < 4; ++i) {
weightSum += weights[i];
ys += weights[i] * ws.y(indices[i]);
}
ys /= weightSum;
return ys;
}
MatrixWorkspace_uptr createInterpolatedWS(MatrixWorkspace &ws, MatrixWorkspace &simulationWS, const std::set<double> &lats, const std::set<double> &longs) {
auto interpWS = Mantid::DataObjects::create<Workspace2D>(ws, simulationWS.histogram(0));
const auto &spectrumInfo = interpWS->spectrumInfo();
for (size_t i = 0; i < interpWS->getNumberHistograms(); ++i) {
double lat, lon;
std::tie(lat, lon) = geographicalAngles(spectrumInfo.position(i));
const auto nearestIndices = nearestNeighbourIndices(lat, lon, lats, longs);
interpWS->mutableY(i) = interpolateY(lat, lon, simulationWS, nearestIndices);
}
return MatrixWorkspace_uptr(interpWS.release());
}
namespace Mantid {
namespace Algorithms {
DECLARE_ALGORITHM(MonteCarloAbsorption)
//------------------------------------------------------------------------------
// Private methods
//------------------------------------------------------------------------------
Russell Taylor
committed
/**
* Initialize the algorithm
*/
void MonteCarloAbsorption::init() {
// The input workspace must have an instrument and units of wavelength
auto wsValidator = boost::make_shared<CompositeValidator>();
wsValidator->add<WorkspaceUnitValidator>("Wavelength");
wsValidator->add<InstrumentValidator>();
declareProperty(make_unique<WorkspaceProperty<>>(
"InputWorkspace", "", Direction::Input, wsValidator),
"The name of the input workspace. The input workspace must "
"have X units of wavelength.");
declareProperty(make_unique<WorkspaceProperty<>>("OutputWorkspace", "",
Direction::Output),
"The name to use for the output workspace.");
auto positiveInt = boost::make_shared<Kernel::BoundedValidator<int>>();
positiveInt->setLower(1);
declareProperty("NumberOfWavelengthPoints", EMPTY_INT(), positiveInt,
"The number of wavelength points for which a simulation is "
"atttempted (default: all points)");
declareProperty(
"EventsPerPoint", DEFAULT_NEVENTS, positiveInt,
"The number of \"neutron\" events to generate per simulated point");
declareProperty("SeedValue", DEFAULT_SEED, positiveInt,
"Seed the random number generator with this value");
InterpolationOption interpolateOpt;
declareProperty(interpolateOpt.property(), interpolateOpt.propertyDoc());
declareProperty("SparseInstrument", false, "Enable simulation on special instrument with a sparse grid of detectors interpolating the results to the real instrument.");
auto sparseDetectorCount = boost::make_shared<Kernel::BoundedValidator<int>>();
positiveInt->setLower(2);
declareProperty("DetectorRows", DEFAULT_LATITUDINAL_DETS, sparseDetectorCount, "Number of detector rows in the detector grid.");
declareProperty("DetectorColumns", DEFAULT_LONGITUDINAL_DETS, sparseDetectorCount, "Number of detector columns in the detector grid.");
}
/**
* Execution code
*/
void MonteCarloAbsorption::exec() {
const MatrixWorkspace_sptr inputWS = getProperty("InputWorkspace");
const int nevents = getProperty("EventsPerPoint");
const int nlambda = getProperty("NumberOfWavelengthPoints");
const int seed = getProperty("SeedValue");
InterpolationOption interpolateOpt;
interpolateOpt.set(getPropertyValue("Interpolation"));
const bool useSparseInstrument = getProperty("SparseInstrument");
auto outputWS = doSimulation(*inputWS, static_cast<size_t>(nevents), nlambda,
seed, interpolateOpt, useSparseInstrument);
setProperty("OutputWorkspace", std::move(outputWS));
/**
* Run the simulation over the whole input workspace
* @param inputWS A reference to the input workspace
* @param nevents Number of MC events per wavelength point to simulate
* @param nlambda Number of wavelength points to simulate. The remainder
* are computed using interpolation
* @param seed Seed value for the random number generator
* @param interpolateOpt Method of interpolation to compute unsimulated points
* @return A new workspace containing the correction factors & errors
*/
MonteCarloAbsorption::doSimulation(const MatrixWorkspace &inputWS,
size_t nevents, int nlambda, int seed,
const InterpolationOption &interpolateOpt,
const bool useSparseInstrument) {
auto outputWS = createOutputWorkspace(inputWS);
SparseInstrumentOption sparseInstrumentOpt(useSparseInstrument);
if (sparseInstrumentOpt.use) {
sparseInstrumentOpt.latitudinalDets = getProperty("DetectorRows");
sparseInstrumentOpt.longitudinalDets = getProperty("DetectorColumns");
}
MatrixWorkspace_uptr simulationWS{sparseInstrumentOpt.chooseSimulationWS(std::move(outputWS))};
const MatrixWorkspace &instrumentWS = sparseInstrumentOpt.chooseInstrumentWS(simulationWS, inputWS);
// Cache information about the workspace that will be used repeatedly
auto instrument = instrumentWS.getInstrument();
const int64_t nhists = static_cast<int64_t>(instrumentWS.getNumberHistograms());
const int nbins = static_cast<int>(instrumentWS.blocksize());
if (isEmpty(nlambda) || nlambda > nbins) {
if (!isEmpty(nlambda)) {
g_log.warning() << "The requested number of wavelength points is larger "
"than the spectra size. "
"Defaulting to spectra size.\n";
}
nlambda = nbins;
}
EFixedProvider efixed(instrumentWS);
auto beamProfile = createBeamProfile(*instrument, inputWS.sample());
// Configure progress
const int lambdaStepSize = nbins / nlambda;
Progress prog(this, 0.0, 1.0, nhists * nbins / lambdaStepSize);
prog.setNotifyStep(0.01);
const std::string reportMsg = "Computing corrections";
// Configure strategy
MCAbsorptionStrategy strategy(*beamProfile, inputWS.sample(), nevents);
const auto &spectrumInfo = simulationWS->spectrumInfo();
PARALLEL_FOR_IF(Kernel::threadSafe(*simulationWS))
for (int64_t i = 0; i < nhists; ++i) {
PARALLEL_START_INTERUPT_REGION
auto &outE = simulationWS->mutableE(i);
if (!spectrumInfo.hasDetectors(i)) {
// Per spectrum values
const auto &detPos = spectrumInfo.position(i);
const double lambdaFixed =
toWavelength(efixed.value(spectrumInfo.detector(i).getID()));
MersenneTwister rng(seed);
auto &outY = simulationWS->mutableY(i);
const auto lambdas = simulationWS->points(i);
// Simulation for each requested wavelength point
for (int j = 0; j < nbins; j += lambdaStepSize) {
const double lambdaStep = lambdas[j];
double lambdaIn(lambdaStep), lambdaOut(lambdaStep);
if (efixed.emode() == DeltaEMode::Direct) {
lambdaIn = lambdaFixed;
} else if (efixed.emode() == DeltaEMode::Indirect) {
lambdaOut = lambdaFixed;
} else {
// elastic case already initialized
}
std::tie(outY[j], std::ignore) =
strategy.calculate(rng, detPos, lambdaIn, lambdaOut);
// Ensure we have the last point for the interpolation
if (lambdaStepSize > 1 && j + lambdaStepSize >= nbins && j + 1 != nbins) {
// Interpolate through points not simulated
if (lambdaStepSize > 1) {
auto histnew = simulationWS->histogram(i);
interpolateOpt.applyInplace(histnew, lambdaStepSize);
simulationWS->setHistogram(i, histnew);
PARALLEL_END_INTERUPT_REGION
PARALLEL_CHECK_INTERUPT_REGION
return simulationWS;
MatrixWorkspace_uptr MonteCarloAbsorption::createOutputWorkspace(
const MatrixWorkspace &inputWS) const {
MatrixWorkspace_uptr outputWS = DataObjects::create<Workspace2D>(inputWS);
// The algorithm computes the signal values at bin centres so they should
// be treated as a distribution
outputWS->setDistribution(true);
outputWS->setYUnit("");
outputWS->setYUnitLabel("Attenuation factor");
return outputWS;
* Create the beam profile. Currently only supports Rectangular. The dimensions
* are either specified by those provided by `SetBeam` algorithm or default
* to the width and height of the samples bounding box
* @param instrument A reference to the instrument object
* @param sample A reference to the sample object
* @return A new IBeamProfile object
std::unique_ptr<IBeamProfile>
MonteCarloAbsorption::createBeamProfile(const Instrument &instrument,
const Sample &sample) const {
const auto frame = instrument.getReferenceFrame();
const auto source = instrument.getSource();
auto beamWidthParam = source->getNumberParameter("beam-width");
auto beamHeightParam = source->getNumberParameter("beam-height");
double beamWidth(-1.0), beamHeight(-1.0);
if (beamWidthParam.size() == 1 && beamHeightParam.size() == 1) {
beamWidth = beamWidthParam[0];
beamHeight = beamHeightParam[0];
} else {
const auto bbox = sample.getShape().getBoundingBox().width();
beamWidth = bbox[frame->pointingHorizontal()];
beamHeight = bbox[frame->pointingUp()];
}
return Mantid::Kernel::make_unique<RectangularBeamProfile>(
*frame, source->getPos(), beamWidth, beamHeight);