Mantid
Loading...
Searching...
No Matches
SparseWorkspace.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
8
10#include "MantidAPI/Run.h"
20#include "MantidHistogramData/HistogramIterator.h"
21#include "MantidKernel/Logger.h"
22
23#include <Poco/DOM/AutoPtr.h>
24#include <Poco/DOM/Document.h>
25
26namespace {
32bool constantIndirectEFixed(const Mantid::API::ExperimentInfo &info, const std::vector<Mantid::detid_t> &detIDs) {
33 if (detIDs.empty())
34 return true;
35 const auto e = info.getEFixed(detIDs[0]);
36 return std::all_of(detIDs.cbegin() + 1, detIDs.cend(), [&](const auto &detID) { return e == info.getEFixed(detID); });
37}
38
39constexpr double R = 1.0; // This will be the default L2 distance.
40
42Mantid::Kernel::Logger g_log("SparseWorkspace");
43} // namespace
44
45namespace Mantid::Algorithms {
46
47SparseWorkspace::SparseWorkspace(const API::MatrixWorkspace &modelWS, const size_t wavelengthPoints, const size_t rows,
48 const size_t columns)
49 : Workspace2D() {
50 double minLat, maxLat, minLong, maxLong;
51 std::tie(minLat, maxLat, minLong, maxLong) = extremeAngles(modelWS);
52 m_gridDef = std::make_unique<Algorithms::DetectorGridDefinition>(minLat, maxLat, rows, minLong, maxLong, columns);
53 if ((rows < 3) || (columns < 3)) {
54 g_log.warning("Can't calculate errors on a sparse workspace with lat or "
55 "long dimension < 3");
56 }
57 const size_t numSpectra = rows * columns;
58 const auto h = modelHistogram(modelWS, wavelengthPoints);
59 initialize(numSpectra, h);
60
61 // Build a quite standard and somewhat complete instrument.
62 auto instrument = std::make_shared<Geometry::Instrument>("MC_simulation_instrument");
63 const auto refFrame = modelWS.getInstrument()->getReferenceFrame();
64
65 instrument->setReferenceFrame(std::make_shared<Geometry::ReferenceFrame>(*refFrame));
66 // The sparse instrument is build around origin.
67 constexpr Kernel::V3D samplePos{0.0, 0.0, 0.0};
68 auto sample = std::make_unique<Geometry::Component>("sample", instrument.get());
69 sample->setPos(samplePos);
70 instrument->add(sample.get());
71 instrument->markAsSamplePos(sample.release());
72 // Add source behind the sample.
73 const Kernel::V3D sourcePos = [&]() {
75 p[refFrame->pointingAlongBeam()] = -2.0 * R;
76 return p;
77 }();
78 auto source = std::make_unique<Geometry::ObjComponent>("source", nullptr, instrument.get());
79 source->setPos(sourcePos);
80 instrument->add(source.get());
81 instrument->markAsSource(source.release());
82
83 auto detShape = makeCubeShape();
84 for (size_t col = 0; col < columns; ++col) {
85 const auto lon = m_gridDef->longitudeAt(col);
86 for (size_t row = 0; row < rows; ++row) {
87 const auto lat = m_gridDef->latitudeAt(row);
88 const size_t index = col * rows + row;
89 const auto detID = static_cast<int>(index);
90 std::ostringstream detName;
91 detName << "det-" << detID;
92 auto det = std::make_unique<Geometry::Detector>(detName.str(), detID, detShape, instrument.get());
93 const Kernel::V3D pos = [&]() {
95 p[refFrame->pointingHorizontal()] = R * std::sin(lon) * std::cos(lat);
96 p[refFrame->pointingUp()] = R * std::sin(lat);
97 p[refFrame->pointingAlongBeam()] = R * std::cos(lon) * std::cos(lat);
98 return p;
99 }();
100 det->setPos(pos);
102 instrument->add(det.get());
103 instrument->markAsDetector(det.release());
104 }
105 }
106 setInstrument(instrument);
107
108 // Copy things needed for the simulation from the model workspace.
110 const auto &modelComponentInfo = modelWS.componentInfo();
111 // Copy beam parameters.
112 const auto modelSource = modelComponentInfo.source();
113 const auto sourceIndex = componentInfo.source();
114 const auto beamShapeParam = modelComponentInfo.getParameterAsString(modelSource, "beam-shape");
115 if (!beamShapeParam.empty())
116 componentInfo.addString(sourceIndex, "beam-shape", beamShapeParam);
117 const auto beamWidthParam = modelComponentInfo.getNumberParameter(modelSource, "beam-width");
118 const auto beamHeightParam = modelComponentInfo.getNumberParameter(modelSource, "beam-height");
119 if (beamWidthParam.size() == 1 && beamHeightParam.size() == 1) {
120 componentInfo.addDouble(sourceIndex, "beam-width", beamWidthParam[0]);
121 componentInfo.addDouble(sourceIndex, "beam-height", beamHeightParam[0]);
122 }
123 const auto beamRadiusParam = modelComponentInfo.getNumberParameter(modelSource, "beam-radius");
124 if (beamRadiusParam.size() == 1) {
125 componentInfo.addDouble(sourceIndex, "beam-radius", beamRadiusParam[0]);
126 }
127 // Add information about EFixed in a proper place.
128 const auto eMode = modelWS.getEMode();
129 mutableRun().addProperty("deltaE-mode", Kernel::DeltaEMode::asString(eMode));
130 if (eMode == Kernel::DeltaEMode::Direct) {
131 mutableRun().addProperty("Ei", modelWS.getEFixed());
132 } else if (eMode == Kernel::DeltaEMode::Indirect) {
133 std::vector<detid_t> detIDs;
134 auto modelInstrument = modelWS.getInstrument();
135 for (size_t i = 0; i < modelWS.getNumberHistograms(); i++) {
136 const auto &spec = modelWS.getSpectrum(i);
137 const auto &specDetIDs = spec.getDetectorIDs();
138 if (!modelInstrument->isMonitor(specDetIDs))
139 detIDs.insert(detIDs.end(), specDetIDs.begin(), specDetIDs.end());
140 }
141 if (detIDs.empty()) {
142 throw std::runtime_error("Sparse instrument: no non-monitor detectors found for Indirect mode.");
143 }
144 if (!constantIndirectEFixed(modelWS, detIDs)) {
145 throw std::runtime_error("Sparse instrument with variable EFixed not supported.");
146 }
147 const auto e = modelWS.getEFixed(detIDs[0]);
148 const auto &sparseDetIDs = detectorInfo().detectorIDs();
149 for (int sparseDetID : sparseDetIDs) {
150 setEFixed(sparseDetID, e);
151 }
152 }
153}
154
156 : Workspace2D(other), m_gridDef(std::make_unique<Algorithms::DetectorGridDefinition>(*other.m_gridDef)) {}
157
164std::tuple<double, double, double, double> SparseWorkspace::extremeAngles(const API::MatrixWorkspace &ws) {
165 const auto &spectrumInfo = ws.spectrumInfo();
166 double minLat = std::numeric_limits<double>::max();
167 double maxLat = std::numeric_limits<double>::lowest();
168 double minLong = std::numeric_limits<double>::max();
169 double maxLong = std::numeric_limits<double>::lowest();
170 for (size_t i = 0; i < ws.getNumberHistograms(); ++i) {
171 if (spectrumInfo.hasDetectors(i)) {
172 double lat, lon;
173 std::tie(lat, lon) = spectrumInfo.geographicalAngles(i);
174 if (lat < minLat) {
175 minLat = lat;
176 }
177 if (lat > maxLat) {
178 maxLat = lat;
179 }
180 if (lon < minLong) {
181 minLong = lon;
182 }
183 if (lon > maxLong) {
184 maxLong = lon;
185 }
186 }
187 }
188 return std::make_tuple(minLat, maxLat, minLong, maxLong);
189}
190
195std::tuple<double, double> SparseWorkspace::extremeWavelengths(const API::MatrixWorkspace &ws) {
196 double currentMin = std::numeric_limits<double>::max();
197 double currentMax = std::numeric_limits<double>::lowest();
198 for (size_t i = 0; i < ws.getNumberHistograms(); ++i) {
199 const auto h = ws.histogram(i);
200 const auto first = h.begin();
201 currentMin = std::min(first->center(), currentMin);
202 const auto last = std::prev(h.end());
203 currentMax = std::max(last->center(), currentMax);
204 }
205 return std::make_tuple(currentMin, currentMax);
206}
207
213Mantid::HistogramData::Histogram SparseWorkspace::modelHistogram(const API::MatrixWorkspace &modelWS,
214 const size_t wavelengthPoints) {
215 double minWavelength, maxWavelength;
216 std::tie(minWavelength, maxWavelength) = extremeWavelengths(modelWS);
217 HistogramData::Frequencies ys(wavelengthPoints, 0.0);
218 HistogramData::FrequencyVariances es(wavelengthPoints, 0.0);
219 HistogramData::Points ps(wavelengthPoints, 0.0);
220 HistogramData::Histogram h(ps, ys, es);
221 auto &xs = h.mutableX();
222 if (wavelengthPoints > 1) {
223 const double step = (maxWavelength - minWavelength) / static_cast<double>(wavelengthPoints - 1);
224 for (size_t i = 0; i < xs.size() - 1; ++i) {
225 xs[i] = minWavelength + step * static_cast<double>(i);
226 }
227 // Force last point as otherwise it might be slightly off due to
228 // small rounding errors in the calculation above.
229 xs.back() = maxWavelength;
230 } else {
231 xs.front() = (minWavelength + maxWavelength) / 2.0;
232 }
233 return h;
234}
235
240 using namespace Poco::XML;
241 const double dimension = 0.05;
242 AutoPtr<Document> shapeDescription = new Document;
243 AutoPtr<Element> typeElement = shapeDescription->createElement("type");
244 typeElement->setAttribute("name", "detector");
245 AutoPtr<Element> shapeElement = shapeDescription->createElement("cuboid");
246 shapeElement->setAttribute("id", "cube");
247 const std::string posCoord = std::to_string(dimension / 2);
248 const std::string negCoord = std::to_string(-dimension / 2);
249 AutoPtr<Element> element = shapeDescription->createElement("left-front-bottom-point");
250 element->setAttribute("x", negCoord);
251 element->setAttribute("y", negCoord);
252 element->setAttribute("z", posCoord);
253 shapeElement->appendChild(element);
254 element = shapeDescription->createElement("left-front-top-point");
255 element->setAttribute("x", negCoord);
256 element->setAttribute("y", posCoord);
257 element->setAttribute("z", posCoord);
258 shapeElement->appendChild(element);
259 element = shapeDescription->createElement("left-back-bottom-point");
260 element->setAttribute("x", negCoord);
261 element->setAttribute("y", negCoord);
262 element->setAttribute("z", negCoord);
263 shapeElement->appendChild(element);
264 element = shapeDescription->createElement("right-front-bottom-point");
265 element->setAttribute("x", posCoord);
266 element->setAttribute("y", negCoord);
267 element->setAttribute("z", posCoord);
268 shapeElement->appendChild(element);
269 typeElement->appendChild(shapeElement);
270 AutoPtr<Element> algebraElement = shapeDescription->createElement("algebra");
271 algebraElement->setAttribute("val", "cube");
272 typeElement->appendChild(algebraElement);
273 Geometry::ShapeFactory shapeFactory;
274 return shapeFactory.createShape(typeElement);
275}
276
284double SparseWorkspace::greatCircleDistance(const double lat1, const double long1, const double lat2,
285 const double long2) {
286 const double latD = std::sin((lat2 - lat1) / 2.0);
287 const double longD = std::sin((long2 - long1) / 2.0);
288 const double S = latD * latD + std::cos(lat1) * std::cos(lat2) * longD * longD;
289 return 2.0 * std::asin(std::sqrt(S));
290}
291
296std::array<double, 4> SparseWorkspace::inverseDistanceWeights(const std::array<double, 4> &distances) {
297 std::array<double, 4> weights;
298 for (size_t i = 0; i < weights.size(); ++i) {
299 if (distances[i] == 0.0) {
300 weights.fill(0.0);
301 weights[i] = 1.0;
302 return weights;
303 }
304 weights[i] = 1.0 / distances[i] / distances[i];
305 }
306 return weights;
307}
308
314HistogramData::HistogramY SparseWorkspace::secondDerivative(const std::array<size_t, 3> &indices,
315 const double distanceStep) const {
316 HistogramData::HistogramY avgSecondDeriv(blocksize());
317
318 HistogramData::HistogramY sumSecondDeriv(blocksize());
319 auto firstDerivB = (y(indices[2]) - y(indices[1])) / distanceStep;
320 auto firstDerivA = (y(indices[1]) - y(indices[0])) / distanceStep;
321 auto secondDerivA = (firstDerivB - firstDerivA) / distanceStep;
322
323 return secondDerivA;
324}
325
331HistogramData::Histogram SparseWorkspace::interpolateFromDetectorGrid(const double lat, const double lon) const {
332 const auto indices = m_gridDef->nearestNeighbourIndices(lat, lon);
333
334 auto h = histogram(0);
335
336 std::array<double, 4> distances;
337 for (size_t i = 0; i < 4; ++i) {
338 double detLat, detLong;
339 std::tie(detLat, detLong) = spectrumInfo().geographicalAngles(indices[i]);
340 distances[i] = greatCircleDistance(lat, lon, detLat, detLong);
341 }
342 const auto weights = inverseDistanceWeights(distances);
343 auto weightSum = weights[0];
344 h.mutableY() = weights[0] * y(indices[0]);
345 for (size_t i = 1; i < 4; ++i) {
346 weightSum += weights[i];
347 h.mutableY() += weights[i] * y(indices[i]);
348 }
349 h.mutableY() /= weightSum;
350 return h;
351}
352
357HistogramData::HistogramE SparseWorkspace::esq(const HistogramData::HistogramE &e) const { return e * e; }
358
363HistogramData::HistogramE SparseWorkspace::esqrt(HistogramData::HistogramE e) const {
364 std::transform(e.cbegin(), e.cend(), e.begin(), [](double f) -> double { return sqrt(f); });
365 return e;
366}
367
377HistogramData::Histogram SparseWorkspace::bilinearInterpolateFromDetectorGrid(const double lat,
378 const double lon) const {
379
380 size_t nearestLatIndex, nearestLonIndex;
381 std::tie(nearestLatIndex, nearestLonIndex) = m_gridDef->getNearestVertex(lat, lon);
382
383 std::array<std::array<size_t, 2>, 2> detIndices;
384 for (size_t i = 0; i < 2; i++) {
385 for (size_t j = 0; j < 2; j++) {
386 detIndices[i][j] = m_gridDef->getDetectorIndex(nearestLatIndex + j, nearestLonIndex + i);
387 }
388 }
389
390 double latLow, longLow, latHigh, longHigh;
391 std::tie(latLow, longLow) = spectrumInfo().geographicalAngles(detIndices[0][0]);
392 std::tie(latHigh, longHigh) = spectrumInfo().geographicalAngles(detIndices[1][1]);
393
394 // interpolate across different longitudes
395 auto ylat1 = ((longHigh - lon) * y(detIndices[0][0]) + (lon - longLow) * y(detIndices[1][0])) / (longHigh - longLow);
396 auto errLat1 = esqrt(esq((longHigh - lon) * e(detIndices[0][0])) + esq((lon - longLow) * e(detIndices[1][0]))) /
397 (longHigh - longLow);
398
399 auto ylat2 = ((longHigh - lon) * y(detIndices[0][1]) + (lon - longLow) * y(detIndices[1][1])) / (longHigh - longLow);
400 auto errLat2 = esqrt(esq((longHigh - lon) * e(detIndices[0][1])) + esq((lon - longLow) * e(detIndices[1][1]))) /
401 (longHigh - longLow);
402
403 // interpolate across different latitudes
404 auto interpY = ((latHigh - lat) * ylat1 + (lat - latLow) * ylat2) / (latHigh - latLow);
405 auto errFromSourcePoints = esqrt(esq((latHigh - lat) * errLat1) + esq((lat - latLow) * errLat2)) / (latHigh - latLow);
406
407 // calculate interpolation errors if possible
408 HistogramData::HistogramE interpolationError(e(0).size(), 0);
409 if ((m_gridDef->numberColumns() > 2) && (m_gridDef->numberRows() > 2)) {
410 // step back a further row\col (if possible) to estimate the second
411 // derivative
412 auto nearestLatIndexSec = nearestLatIndex > 0 ? nearestLatIndex - 1 : 0;
413 auto nearestLonIndexSec = nearestLonIndex > 0 ? nearestLonIndex - 1 : 0;
414 std::array<size_t, 3> threeIndices;
415
416 // 2nd derivative in longitude
417 for (int i = 0; i < 3; i++) {
418 threeIndices[i] = m_gridDef->getDetectorIndex(nearestLatIndex, nearestLonIndexSec + i);
419 }
420 auto avgSecondDerivLong = secondDerivative(threeIndices, longHigh - longLow);
421
422 // 2nd derivative in latitude
423 for (int i = 0; i < 3; i++) {
424 threeIndices[i] = m_gridDef->getDetectorIndex(nearestLatIndexSec + i, nearestLonIndex);
425 }
426 auto avgSecondDerivLat = secondDerivative(threeIndices, latHigh - latLow);
427
428 // calculate the interpolation error according to a Taylor expansion from
429 // the low lat and low long points. Doesn't make any difference if do this
430 // in angle or distance because scaling factor (~ radius) cancels out
431 HistogramData::HistogramY interpolationErrorAsYData =
432 0.5 * (lon - longLow) * (longHigh - lon) * avgSecondDerivLong +
433 0.5 * (lat - latLow) * (latHigh - lat) * avgSecondDerivLat;
434 // convert from HistogramY to HistogramE
435 interpolationError = interpolationErrorAsYData.rawData();
436 }
437
438 auto h = histogram(0);
439 h.mutableY() = interpY;
440 // combine the (independent) errors
441 // Note - interpolationError may be negative if the 2nd derivative is negative
442 // so the following line also has useful side effect of taking abs value
443 h.mutableE() = esqrt(esq(interpolationError) + esq(errFromSourcePoints));
444 return h;
445}
446
448
449} // namespace Mantid::Algorithms
std::map< DeltaEMode::Type, std::string > index
This class is shared by a few Workspace types and holds information related to a particular experimen...
Run & mutableRun()
Writable version of the run object.
const SpectrumInfo & spectrumInfo() const
Return a reference to the SpectrumInfo object.
Geometry::ComponentInfo & mutableComponentInfo()
const Geometry::DetectorInfo & detectorInfo() const
Return a const reference to the DetectorInfo object.
double getEFixed(const detid_t detID) const
Easy access to the efixed value for this run & detector ID.
Geometry::Instrument_const_sptr getInstrument() const
Returns the parameterized instrument.
Kernel::DeltaEMode::Type getEMode() const
Returns the emode for this run.
const Sample & sample() const
Sample accessors.
void setEFixed(const detid_t detID, const double value)
Set the efixed value for a given detector ID.
const Geometry::ComponentInfo & componentInfo() const
void setInstrument(const Geometry::Instrument_const_sptr &instr)
Instrument accessors.
void setDetectorID(const detid_t detID)
Clear the list of detector IDs, then add one.
Definition ISpectrum.cpp:84
const std::set< detid_t > & getDetectorIDs() const
Get a const reference to the detector IDs set.
void addProperty(Kernel::Property *prop, bool overwrite=false)
Add data to the object in the form of a property.
Definition LogManager.h:90
Base MatrixWorkspace Abstract Class.
virtual ISpectrum & getSpectrum(const size_t index)=0
Return the underlying ISpectrum ptr at the given workspace index.
const HistogramData::HistogramE & e(const size_t index) const
void initialize(const std::size_t &NVectors, const std::size_t &XLength, const std::size_t &YLength)
Initialize the workspace.
virtual std::size_t getNumberHistograms() const =0
Returns the number of histograms in the workspace.
HistogramData::Histogram histogram(const size_t index) const
Returns the Histogram at the given workspace index.
bool hasDetectors(const size_t index) const
Returns true if the spectrum is associated with detectors in the instrument.
std::pair< double, double > geographicalAngles(const size_t index) const
Calculate latitude and longitude for given spectrum index.
DetectorGridDefinition is a helper class for building the sparse instrument in MonteCarloAbsorption.
Defines functions and utilities to create and deal with sparse instruments.
static std::array< double, 4 > inverseDistanceWeights(const std::array< double, 4 > &distances)
Calculate the inverse distance weights for the given distances.
HistogramData::HistogramE esq(const HistogramData::HistogramE &e) const
Square the error values in a histogram.
static std::tuple< double, double > extremeWavelengths(const API::MatrixWorkspace &ws)
Find the maximum and minimum wavelength points over the entire workpace.
static HistogramData::Histogram modelHistogram(const API::MatrixWorkspace &modelWS, const size_t wavelengthPoints)
Create a template histogram for the sparse instrument workspace.
HistogramData::HistogramY secondDerivative(const std::array< size_t, 3 > &indices, const double distanceStep) const
Calculate the second derivative of a histogram along a row of indices.
std::unique_ptr< Algorithms::DetectorGridDefinition > m_gridDef
static double greatCircleDistance(const double lat1, const double long1, const double lat2, const double long2)
Calculate the distance between two points on a unit sphere.
HistogramData::HistogramE esqrt(HistogramData::HistogramE e) const
Square root the error values in a histogram.
static std::tuple< double, double, double, double > extremeAngles(const API::MatrixWorkspace &ws)
Find the latitude and longitude intervals the detectors of the given workspace span as seen from the ...
Mantid::Geometry::IObject_sptr makeCubeShape()
Creates a rectangular cuboid shape.
virtual HistogramData::Histogram interpolateFromDetectorGrid(const double lat, const double lon) const
Spatially interpolate a single histogram from nearby detectors.
SparseWorkspace * doClone() const override
Virtual clone method. Not implemented to force implementation in children.
virtual HistogramData::Histogram bilinearInterpolateFromDetectorGrid(const double lat, const double lon) const
Spatially interpolate a single histogram from nearby detectors using bilinear interpolation method.
SparseWorkspace(const API::MatrixWorkspace &modelWS, const size_t wavelengthPoints, const size_t rows, const size_t columns)
Concrete workspace implementation.
Definition Workspace2D.h:30
std::size_t size() const override
get pseudo size
Histogram1D & getSpectrum(const size_t index) override
Return the underlying ISpectrum ptr at the given workspace index.
Definition Workspace2D.h:64
std::size_t blocksize() const override
get the size of each vector
void addString(const size_t componentIndex, const std::string &name, const std::string &value, const std::string *const pDescription=nullptr, const std::string &pVisible="true")
Add or replace a named string parameter at a specific component index.
void addDouble(const size_t componentIndex, const std::string &name, double value, const std::string *const pDescription=nullptr, const std::string &pVisible="true")
Add or replace a named double parameter at a specific component index.
const std::vector< detid_t > & detectorIDs() const
Returns a sorted vector of all detector IDs.
Class originally intended to be used with the DataHandling 'LoadInstrument' algorithm.
std::shared_ptr< CSGObject > createShape(Poco::XML::Element *pElem)
Creates a geometric object from a DOM-element-node pointing to an element whose child nodes contain t...
The Logger class is in charge of the publishing messages from the framework through various channels.
Definition Logger.h:51
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
Class for 3D vectors.
Definition V3D.h:35
Kernel::Logger g_log("DetermineSpinStateOrder")
std::shared_ptr< IObject > IObject_sptr
Typdef for a shared pointer.
Definition IObject.h:106
static constexpr double h
Planck constant in J*s.
STL namespace.
std::string to_string(const wide_integer< Bits, Signed > &n)
static std::string asString(const Type mode)
Return a string representation of the given mode.