Mantid
Loading...
Searching...
No Matches
IntegrateQLabEvents.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 +
11#include "MantidKernel/Logger.h"
13
14#include <boost/math/special_functions/round.hpp>
15#include <cmath>
16#include <fstream>
17#include <memory>
18#include <numeric>
19#include <tuple>
20
21extern "C" {
22#include <cstdio>
23#include <utility>
24
25#include <gsl/gsl_eigen.h>
26#include <gsl/gsl_matrix.h>
27#include <gsl/gsl_vector.h>
28}
29
30using namespace Mantid::DataObjects;
31namespace Mantid {
33
34namespace MDAlgorithms {
35
36using namespace std;
39
40namespace {
42Kernel::Logger g_log("CostFuncUnweightedLeastSquares");
43} // namespace
44
46 const bool useOnePercentBackgroundCorrection)
47 : m_radius(radius), m_useOnePercentBackgroundCorrection(useOnePercentBackgroundCorrection) {
49 for (size_t peakIndex = 0; peakIndex != peak_q_list.size(); ++peakIndex) {
50 const V3D q = peak_q_list[peakIndex].second;
51 CellCoords abc(q, m_cellSize);
52 // abc = [0, 0, 0] is no scattering
53 if (!abc.isOrigin()) {
54 SlimEvents events; // empty list
55 OccupiedCell c = {peakIndex, q, events};
56 std::pair<size_t, OccupiedCell> newCell(abc.getHash(), c);
57 m_cellsWithPeaks.insert(newCell);
58 }
59 }
60}
61
67
68bool IntegrateQLabEvents::isOrigin(const V3D &q, const double &cellSize) {
69 int64_t a(static_cast<int64_t>(q[0] / cellSize));
70 int64_t b(static_cast<int64_t>(q[1] / cellSize));
71 int64_t c(static_cast<int64_t>(q[2] / cellSize));
72 return !(a || b || c);
73}
74
76 for (const auto &event_q : event_qs)
77 addEvent(event_q);
78}
79
80// Entry function that perform the integration
82IntegrateQLabEvents::ellipseIntegrateEvents(const std::vector<V3D> &E1Vec, V3D const &peak_q, bool specify_size,
83 double peak_radius, double back_inner_radius, double back_outer_radius,
84 std::vector<double> &axes_radii, double &inti, double &sigi,
85 std::pair<double, double> &backi) {
86 inti = 0.0; // default values, in case something
87 sigi = 0.0; // is wrong with the peak.
88 backi = std::make_pair<double, double>(0.0, 0.0);
89
90 int64_t hash = CellCoords(peak_q, m_cellSize).getHash();
91 auto cell_it = m_cellsWithPeaks.find(hash);
92 if (cell_it == m_cellsWithPeaks.end())
93 return std::make_shared<NoShape>(); // peak_q is [0, 0, 0]
94 OccupiedCell cell = cell_it->second;
95 SlimEvents &some_events = cell.events;
96 if (some_events.size() < 3)
97 return std::make_shared<NoShape>();
98
99 DblMatrix cov_matrix(3, 3);
100 makeCovarianceMatrix(some_events, cov_matrix, m_radius);
101
102 std::vector<V3D> eigen_vectors;
103 std::vector<double> eigen_values;
104 getEigenVectors(cov_matrix, eigen_vectors, eigen_values);
105
106 std::vector<double> sigmas(3);
107 for (int i = 0; i < 3; i++)
108 sigmas[i] = sqrt(eigen_values[i]);
109
110 bool invalid_peak =
111 std::any_of(sigmas.cbegin(), sigmas.cend(), [](const double sigma) { return std::isnan(sigma) || sigma <= 0; });
112
113 // if data collapses to a line or plane, the ellipsoid volume is zero
114 if (invalid_peak)
115 return std::make_shared<NoShape>();
116
117 return ellipseIntegrateEvents(E1Vec, peak_q, some_events, eigen_vectors, sigmas, specify_size, peak_radius,
118 back_inner_radius, back_outer_radius, axes_radii, inti, sigi, backi);
119}
120
121std::pair<double, double> IntegrateQLabEvents::numInEllipsoid(SlimEvents const &events,
122 std::vector<V3D> const &directions,
123 std::vector<double> const &sizes) {
124 std::pair<double, double> count(0, 0);
125 for (const auto &event : events) {
126 double sum = 0;
127 for (size_t k = 0; k < 3; k++) {
128 double comp = event.second.scalar_prod(directions[k]) / sizes[k];
129 sum += comp * comp;
130 }
131 if (sum <= 1) {
132 count.first += event.first.first; // count
133 count.second += event.first.second; // error squared (add in quadrature)
134 }
135 }
136 return count;
137}
138
139std::pair<double, double> IntegrateQLabEvents::numInEllipsoidBkg(SlimEvents const &events,
140 std::vector<V3D> const &directions,
141 std::vector<double> const &sizes,
142 std::vector<double> const &sizesIn,
143 const bool useOnePercentBackgroundCorrection) {
144 std::pair<double, double> count(0, 0);
145 std::vector<std::pair<double, double>> eventVec;
146 for (const auto &event : events) {
147 double sum = 0;
148 double sumIn = 0;
149 for (size_t k = 0; k < 3; k++) {
150 double comp = event.second.scalar_prod(directions[k]) / sizes[k];
151 sum += comp * comp;
152 comp = event.second.scalar_prod(directions[k]) / sizesIn[k];
153 sumIn += comp * comp;
154 }
155 if (sum <= 1 && sumIn >= 1)
156 eventVec.emplace_back(event.first);
157 }
158
159 // NOTE:
160 // [for SNS only]
161 // Some event has weight great than 1, which is corrected using the following prunning
162 // by removing the top 1% events with higher weights.
163 // It is worth pointing out that this pruning is (to the best) an rough estimate as it
164 // will most likely either over-prunning (remove some events with weight of 1) or under-
165 // pruning (did not remove all events with weights greater than 1).
166 auto endIndex = eventVec.size();
167 if (useOnePercentBackgroundCorrection) {
168 // Remove top 1%
169 std::sort(eventVec.begin(), eventVec.end(),
170 [](const std::pair<double, double> &a, const std::pair<double, double> &b) { return a.first < b.first; });
171 endIndex = static_cast<size_t>(0.99 * static_cast<double>(endIndex));
172 }
173
174 for (size_t k = 0; k < endIndex; ++k) {
175 count.first += eventVec[k].first;
176 count.second += eventVec[k].second;
177 }
178
179 return count;
180}
181
183 double totalCounts;
184 for (int row = 0; row < 3; row++) {
185 for (int col = 0; col < 3; col++) {
186 totalCounts = 0;
187 double sum = 0;
188 for (const auto &event : events) {
189 if (event.second.norm() <= radius) {
190 totalCounts += event.first.first;
191 sum += event.first.first * event.second[row] * event.second[col];
192 }
193 }
194 if (totalCounts > 1)
195 matrix[row][col] = sum / (totalCounts - 1);
196 else
197 matrix[row][col] = sum;
198 }
199 }
200}
201
202void IntegrateQLabEvents::getEigenVectors(DblMatrix const &cov_matrix, std::vector<V3D> &eigen_vectors,
203 std::vector<double> &eigen_values) {
204 unsigned int size = 3;
205
206 gsl_matrix *matrix = gsl_matrix_alloc(size, size);
207 gsl_vector *eigen_val = gsl_vector_alloc(size);
208 gsl_matrix *eigen_vec = gsl_matrix_alloc(size, size);
209 gsl_eigen_symmv_workspace *wkspace = gsl_eigen_symmv_alloc(size);
210
211 // copy the matrix data into the gsl matrix
212 for (size_t row = 0; row < size; row++)
213 for (size_t col = 0; col < size; col++) {
214 gsl_matrix_set(matrix, row, col, cov_matrix[row][col]);
215 }
216
217 gsl_eigen_symmv(matrix, eigen_val, eigen_vec, wkspace);
218
219 // copy the resulting eigen vectors to output vector
220 for (size_t col = 0; col < size; col++) {
221 eigen_vectors.emplace_back(gsl_matrix_get(eigen_vec, 0, col), gsl_matrix_get(eigen_vec, 1, col),
222 gsl_matrix_get(eigen_vec, 2, col));
223 eigen_values.emplace_back(gsl_vector_get(eigen_val, col));
224 }
225
226 gsl_matrix_free(matrix);
227 gsl_vector_free(eigen_val);
228 gsl_matrix_free(eigen_vec);
229 gsl_eigen_symmv_free(wkspace);
230}
231
233 V3D q(event.second);
234 CellCoords abc(q, m_cellSize);
235 if (abc.isOrigin())
236 return;
237 int64_t hash = abc.getHash();
238 auto cell_it = m_cellsWithEvents.find(hash);
239 if (cell_it == m_cellsWithEvents.end()) { // create a new cell
240 SlimEvents events = {event};
241 std::pair<size_t, SlimEvents> newCell(hash, events);
242 m_cellsWithEvents.insert(newCell);
243 } else
244 cell_it->second.emplace_back(event);
245}
246
248IntegrateQLabEvents::ellipseIntegrateEvents(const std::vector<V3D> &E1Vec, V3D const &peak_q, SlimEvents const &ev_list,
249 std::vector<V3D> const &directions, std::vector<double> const &sigmas,
250 bool specify_size, double peak_radius, double back_inner_radius,
251 double back_outer_radius, std::vector<double> &axes_radii, double &inti,
252 double &sigi, std::pair<double, double> &backi) {
253 /* r1, r2 and r3 will give the sizes of the major axis of the peak
254 * ellipsoid, and of the inner and outer surface of the background
255 * ellipsoidal shell, respectively.
256 * They will specify the size as the number of standard deviations
257 * in the direction of each of the principal axes that the ellipsoid
258 * will extend from the center. */
259 double r1, r2, r3;
260
261 double max_sigma = sigmas[0];
262 for (int i = 1; i < 3; i++) {
263 if (sigmas[i] > max_sigma) {
264 max_sigma = sigmas[i];
265 }
266 }
267
268 if (specify_size) {
269 /* scale specified sizes by 1/max_sigma so when multiplied by the
270 * individual sigmas in different directions, the major axis has
271 * the specified size */
272 r1 = peak_radius / max_sigma;
273 r2 = back_inner_radius / max_sigma;
274 r3 = back_outer_radius / max_sigma;
275 } else {
276 r1 = 3;
277 r2 = 3;
278 r3 = r2 * 1.25992105; // A factor of 2 ^ (1/3) will make the background
279 /* shell volume equal to the peak region volume. If necessary,
280 * restrict the background ellipsoid to lie within the specified
281 * sphere, and adjust the other sizes, proportionally */
282 if (r3 * max_sigma > m_radius) {
283 r3 = m_radius / max_sigma;
284 r1 = r3 * 0.79370053f; // This value for r1 and r2 makes the background
285 r2 = r1; // shell volume equal to the peak region volume.
286 }
287 }
288
289 axes_radii.clear();
290 std::vector<double> abcBackgroundOuterRadii;
291 std::vector<double> abcBackgroundInnerRadii;
292 std::vector<double> abcRadii;
293 for (int i = 0; i < 3; i++) {
294 abcBackgroundOuterRadii.emplace_back(r3 * sigmas[i]);
295 abcBackgroundInnerRadii.emplace_back(r2 * sigmas[i]);
296 abcRadii.emplace_back(r1 * sigmas[i]);
297 axes_radii.emplace_back(r1 * sigmas[i]);
298 }
299
300 if (!E1Vec.empty()) {
301 double h3 = 1.0 - detectorQ(E1Vec, peak_q, abcBackgroundOuterRadii);
302 // scaled from area of circle minus segment when r normalized to 1
303 double m3 = std::sqrt(1.0 - (std::acos(1.0 - h3) - (1.0 - h3) * std::sqrt(2.0 * h3 - h3 * h3)) / M_PI);
304 double h1 = 1.0 - detectorQ(E1Vec, peak_q, axes_radii);
305 // Do not use peak if edge of detector is inside integration radius
306 if (h1 > 0.0)
307 return std::make_shared<const PeakShapeEllipsoid>(directions, abcRadii, abcBackgroundInnerRadii,
308 abcBackgroundOuterRadii, Mantid::Kernel::QLab,
309 "IntegrateEllipsoids");
310 r3 *= m3;
311 if (r2 != r1) {
312 double h2 = 1.0 - detectorQ(E1Vec, peak_q, abcBackgroundInnerRadii);
313 // scaled from area of circle minus segment when r normalized to 1
314 double m2 = std::sqrt(1.0 - (std::acos(1.0 - h2) - (1.0 - h2) * std::sqrt(2.0 * h2 - h2 * h2)) / M_PI);
315 r2 *= m2;
316 }
317 }
318
319 std::pair<double, double> backgrd = numInEllipsoidBkg(ev_list, directions, abcBackgroundOuterRadii,
320 abcBackgroundInnerRadii, m_useOnePercentBackgroundCorrection);
321
322 std::pair<double, double> peak_w_back = numInEllipsoid(ev_list, directions, axes_radii);
323
324 double ratio = pow(r1, 3) / (pow(r3, 3) - pow(r2, 3));
325 if (r3 == r2) {
326 // special case if background radius = peak radius, force background to zero
327 ratio = 1.0;
328 backgrd.first = 0.0;
329 backgrd.second = 0.0;
330 }
331
332 inti = peak_w_back.first - ratio * backgrd.first;
333 sigi = sqrt(peak_w_back.second + ratio * ratio * backgrd.second);
334 backi = std::make_pair<double, double>(backgrd.first * ratio, backgrd.second * ratio * ratio);
335
336 if (inti < 0) {
337 std::ostringstream msg;
338 msg << "Negative intensity found: " << inti << "\n"
339 << "Please use slice viewer to check the peak with negative intensity to decide:\n"
340 << "-- adjust peak and background raidus\n"
341 << "-- prune false positive indexation results\n";
342 g_log.notice() << msg.str();
343 // debug message
344 std::ostringstream debugmsg;
345 debugmsg << "peak_radius = " << peak_radius << "\n"
346 << "back_inner_radius = " << back_inner_radius << "\n"
347 << "back_outer_radius = " << back_outer_radius << "\n"
348 << "sigmas = (" << sigmas[0] << "," << sigmas[1] << "," << sigmas[2] << ")\n"
349 << "r1, r2, r3 = " << r1 << "," << r2 << "," << r3 << "\n"
350 << "peak_w_back.first = " << peak_w_back.first << "\n"
351 << "backgrd.first = " << backgrd.first << "\n"
352 << "ratio = " << ratio << "\n"
353 << "inti = peak_w_back.first - ratio * backgrd.first = " << inti << "\n";
354 g_log.debug() << debugmsg.str();
355 }
356
357 // Make the shape and return it.
358 return std::make_shared<const PeakShapeEllipsoid>(directions, abcRadii, abcBackgroundInnerRadii,
359 abcBackgroundOuterRadii, Mantid::Kernel::QLab,
360 "IntegrateEllipsoids");
361}
362double IntegrateQLabEvents::detectorQ(const std::vector<V3D> &E1Vec, const Kernel::V3D &QLabFrame,
363 const std::vector<double> &r) {
364 double quot = 1.0;
365 for (auto &E1 : E1Vec) {
366 V3D distv = QLabFrame - E1 * (QLabFrame.scalar_prod(E1)); // distance to the trajectory as a vector
367 double quot0 = distv.norm() / *(std::min_element(r.begin(), r.end()));
368 if (quot0 < quot) {
369 quot = quot0;
370 }
371 }
372 return quot;
373}
374
376 for (auto &cell_it : m_cellsWithPeaks) {
377 OccupiedCell &cell = cell_it.second;
378 CellCoords abc(cell.peakQ, m_cellSize);
379 for (const int64_t &hash : abc.nearbyCellHashes()) {
380 auto cellE_it = m_cellsWithEvents.find(hash);
381 if (cellE_it != m_cellsWithEvents.end()) {
382 for (const SlimEvent &event : cellE_it->second) {
383 SlimEvent neighborEvent = event; // copy
384 neighborEvent.second = event.second - cell.peakQ;
385 cell.events.emplace_back(neighborEvent);
386 }
387 }
388 }
389 }
390}
391
392} // namespace MDAlgorithms
393} // namespace Mantid
double sigma
Definition: GetAllEi.cpp:156
int count
counter
Definition: Matrix.cpp:37
double radius
Definition: Rasterize.cpp:31
Helper class for reporting progress from algorithms.
Definition: Progress.h:25
The Logger class is in charge of the publishing messages from the framework through various channels.
Definition: Logger.h:52
void debug(const std::string &msg)
Logs at debug level.
Definition: Logger.cpp:114
void notice(const std::string &msg)
Logs at notice level.
Definition: Logger.cpp:95
Class for 3D vectors.
Definition: V3D.h:34
constexpr double scalar_prod(const V3D &v) const noexcept
Calculates the cross product.
Definition: V3D.h:274
double norm() const noexcept
Definition: V3D.h:263
std::unordered_map< size_t, SlimEvents > m_cellsWithEvents
list of cells occupied with events
double detectorQ(const std::vector< V3D > &E1Vec, const V3D &QLabFrame, const std::vector< double > &r)
Calculate if this Q is on a detector.
static bool isOrigin(const V3D &q, const double &cellSize)
Determine if an input Q-vector lies in the cell associated to the origin.
void addEvent(const SlimEvent event)
assign an event to one cell of the partitioned QLab space.
double m_cellSize
size of the square cell unit, holding at most one single peak
static std::pair< double, double > numInEllipsoidBkg(SlimEvents const &events, std::vector< V3D > const &directions, std::vector< double > const &sizes, std::vector< double > const &sizesIn, const bool useOnePercentBackgroundCorrection)
Number of events in an ellipsoid with background correction.
void setRadius(const double &radius)
Set peak integration radius.
const bool m_useOnePercentBackgroundCorrection
if one percent culling of the background should be performed.
static void getEigenVectors(Kernel::DblMatrix const &cov_matrix, std::vector< V3D > &eigen_vectors, std::vector< double > &eigen_values)
Eigen vectors of a 3x3 real symmetric matrix using the GSL.
static std::pair< double, double > numInEllipsoid(SlimEvents const &events, std::vector< V3D > const &directions, std::vector< double > const &sizes)
Number of events in an ellipsoid.
void populateCellsWithPeaks()
Assign events to each of the cells occupied by events.
std::unordered_map< size_t, OccupiedCell > m_cellsWithPeaks
list the occupied cells in an unordered map for fast searching
PeakShape_const_sptr ellipseIntegrateEvents(const std::vector< V3D > &E1Vec, V3D const &peak_q, bool specify_size, double peak_radius, double back_inner_radius, double back_outer_radius, std::vector< double > &axes_radii, double &inti, double &sigi, std::pair< double, double > &backi)
Integrate the events around the specified peak QLab vector.
static void makeCovarianceMatrix(SlimEvents const &events, Kernel::DblMatrix &matrix, double radius)
3x3 covariance matrix of a list of SlimEvent objects
IntegrateQLabEvents(const SlimEvents &peak_q_list, double radius, const bool useOnePercentBackgroundCorrection=true)
Store events within a certain radius of the specified peak centers, and sum these events to estimate ...
void addEvents(SlimEvents const &event_qs)
distribute the events among the cells of the partitioned QLab space.
std::complex< double > MANTID_API_DLL E1(std::complex< double > z)
Integral for Gamma.
Kernel::Logger g_log("ExperimentInfo")
static logger object
std::shared_ptr< const PeakShapeEllipsoid > PeakShapeEllipsoid_const_sptr
std::shared_ptr< const PeakShape > PeakShape_const_sptr
Definition: PeakShape.h:43
std::vector< SlimEvent > SlimEvents
std::pair< std::pair< double, double >, V3D > SlimEvent
Helper class which provides the Collimation Length for SANS instruments.
STL namespace.
Partition QLab space into a cubic lattice.
bool isOrigin()
Check if all cell coords are zero.
std::vector< int64_t > nearbyCellHashes()
Hashes for the 26 first neighbor coordinates plus the coordinates themselves.
int64_t getHash()
cast coordinates to scalar, to be used as key in an unordered map