Mantid
Loading...
Searching...
No Matches
FakeMD.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 +
7//--------------------------------------------------------------------------------------------------
8// Includes
9//--------------------------------------------------------------------------------------------------
11
18#include "MantidKernel/Utils.h"
20
21#include "boost/math/distributions.hpp"
22
23namespace Mantid::DataObjects {
24
25using Kernel::ThreadPool;
26using Kernel::ThreadSchedulerFIFO;
27
38FakeMD::FakeMD(const std::vector<double> &uniformParams, const std::vector<double> &peakParams,
39 const std::vector<double> &ellipsoidParams, const int randomSeed, const bool randomizeSignal)
40 : m_uniformParams(uniformParams), m_peakParams(peakParams), m_ellipsoidParams(ellipsoidParams),
41 m_randomSeed(randomSeed), m_randomizeSignal(randomizeSignal), m_detIDs(), m_randGen(1), m_uniformDist() {
42 if (uniformParams.empty() && peakParams.empty() && ellipsoidParams.empty()) {
43 throw std::invalid_argument("You must specify at least one of peakParams, "
44 "ellipsoidParams or uniformParams");
45 }
46}
47
55
56 CALL_MDEVENT_FUNCTION(this->addFakePeak, workspace)
59
60 // Mark that events were added, so the file back end (if any) needs updating
61 workspace->setFileNeedsUpdating(true);
62}
63
70 try {
71 auto inst = workspace.getExperimentInfo(0)->getInstrument();
72 m_detIDs = inst->getDetectorIDs(true); // true=skip monitors
73 size_t max = m_detIDs.size() - 1;
75 } catch (std::invalid_argument &) {
76 }
77}
78
85template <typename MDE, size_t nd> void FakeMD::addFakePeak(typename MDEventWorkspace<MDE, nd>::sptr ws) {
86 if (m_peakParams.empty())
87 return;
88
89 if (m_peakParams.size() != nd + 2)
90 throw std::invalid_argument("PeakParams needs to have ndims+2 arguments.");
91 if (m_peakParams[0] <= 0)
92 throw std::invalid_argument("PeakParams: number_of_events needs to be > 0");
93 auto num = size_t(m_peakParams[0]);
94
95 // Width of the peak
96 auto desiredRadius = m_peakParams.back();
97
98 std::mt19937 rng(static_cast<unsigned int>(m_randomSeed));
99 std::uniform_real_distribution<coord_t> flat(0, 1.0);
100 Kernel::normal_distribution<coord_t> normal(0.0, 1.0); // mean = 0, std = 1
101
102 // Inserter to help choose the correct event type
104
105 for (size_t i = 0; i < num; ++i) {
106
107 // generate point along radius with ^1/n scaling for uniformity (i.e. Prob(point < r) ~ r^nd)
108 auto radius_frac = flat(rng);
109 auto radius = static_cast<coord_t>(desiredRadius * pow(radius_frac, 1.0f / nd));
110 // to get direction generate uniformly distributed points on surface of a unit N sphere using
111 // uniformly dist. rand numbers as per http://corysimon.github.io/articles/uniformdistn-on-sphere/
112 coord_t centers[nd];
113 coord_t modulusSq = 0;
114 while (modulusSq < 1E-6) {
115 // small modulus may cause issues with floating point precision when dividing later
116 for (size_t d = 0; d < nd; d++) {
117 centers[d] = normal(rng);
118 modulusSq += centers[d] * centers[d];
119 }
120 }
121 // now normalise by modulus, scale by radius and translate to peak centre
122 auto inverseModulus = static_cast<coord_t>(1.0f / sqrt(modulusSq));
123 for (size_t d = 0; d < nd; d++) {
124 // Multiply by the scaling and the desired peak radius
125 centers[d] *= radius * inverseModulus;
126 // Also offset by the center of the peak, as taken in Params
127 centers[d] += static_cast<coord_t>(m_peakParams[d + 1]);
128 }
129
130 // Default or randomized error/signal
131 float signal = 1.0;
132 float errorSquared = 1.0;
133 if (m_randomizeSignal) {
134 signal = float(0.5 + flat(rng));
135 errorSquared = float(0.5 + flat(rng));
136 }
137
138 // Create and add the event.
139 eventHelper.insertMDEvent(signal, errorSquared, 0, 0, pickDetectorID(),
140 centers); // 0 = associated experiment-info index
141 }
142
143 ws->splitBox();
144 auto *ts = new ThreadSchedulerFIFO();
145 ThreadPool tp(ts);
146 ws->splitAllIfNeeded(ts);
147 tp.joinAll();
148 ws->refreshCache();
149}
150
161template <typename MDE, size_t nd> void FakeMD::addFakeEllipsoid(typename MDEventWorkspace<MDE, nd>::sptr ws) {
162 if (m_ellipsoidParams.empty())
163 return;
164
165 if (m_ellipsoidParams.size() != 2 + 2 * nd + nd * nd)
166 throw std::invalid_argument("EllipsoidParams: incorrect number of parameters.");
167 if (m_ellipsoidParams[0] <= 0)
168 throw std::invalid_argument("EllipsoidParams: number_of_events needs to be > 0");
169
170 // extract input parameters
171 auto numEvents = size_t(m_ellipsoidParams[0]);
172 coord_t center[nd];
173 Kernel::Matrix<double> Evec(nd, nd); // hold eigenvectors
175 nd); // hold sqrt(eigenvals) standard devs on diag
176 for (size_t n = 0; n < nd; n++) {
177 center[n] = static_cast<coord_t>(m_ellipsoidParams[n + 1]);
178 // get row/col index for eigenvector matrix
179 for (size_t d = 0; d < nd; d++) {
180 Evec[d][n] = m_ellipsoidParams[1 + nd + n * nd + d];
181 }
182 stds[n][n] = sqrt(m_ellipsoidParams[m_ellipsoidParams.size() - (1 + nd) + n]);
183 }
184 auto doCounts = m_ellipsoidParams[m_ellipsoidParams.size() - 1];
185
186 // get affine transformation that maps unit variance spherical
187 // normal dist to ellipsoid
188 auto A = Evec * stds;
189
190 // calculate inverse of covariance matrix (if necassary)
191 Kernel::Matrix<double> invCov(nd, nd);
192 if (doCounts > 0) {
193 auto var = stds * stds;
194 // copy Evec to a matrix to hold inverse
195 Kernel::Matrix<double> invEvec(Evec.getVector()); // hold eigenvectors
196 // invert Evec matrix
197 invEvec.Invert();
198 // find covariance matrix to invert
199 invCov = Evec * var * invEvec; // covar mat
200 // invert covar matrix
201 invCov.Invert();
202 }
203 // get chi-squared boost function
204 boost::math::chi_squared chisq(nd);
205
206 // prepare random number generator
207 std::mt19937 rng(static_cast<unsigned int>(m_randomSeed));
208 Kernel::normal_distribution<double> d(0.0, 1.0); // mean = 0, std = 1
209
210 // Inserter to help choose the correct event type
212
213 for (size_t iEvent = 0; iEvent < numEvents; ++iEvent) {
214
215 // sample uniform normal distribution
216 std::vector<double> pos;
217 for (size_t n = 0; n < nd; n++) {
218 pos.push_back(d(rng));
219 }
220 // apply affine transformation
221 pos = A * pos;
222
223 // calculate counts
224 float signal = 1.0;
225 float errorSquared = 1.0;
226 if (doCounts > 0) {
227 // calculate Mahalanobis distance
228 // https://en.wikipedia.org/wiki/Mahalanobis_distance
229
230 // md = sqrt(pos.T * invCov * pos)
231 auto tmp = invCov * pos;
232 double mdsq = 0.0;
233 for (size_t n = 0; n < nd; n++) {
234 mdsq += pos[n] * tmp[n];
235 }
236 // for a multivariate normal dist m-dist is distribute
237 // as chi-squared pdf with nd degrees of freedom
238 signal = static_cast<float>(boost::math::pdf(chisq, sqrt(mdsq)));
239 errorSquared = signal;
240 }
241 // convert pos to coord_t and offset by center
242 coord_t eventCenter[nd];
243 for (size_t n = 0; n < nd; n++) {
244 eventCenter[n] = static_cast<coord_t>(pos[n] + center[n]);
245 }
246
247 // add event (need to convert pos to coord_t)
248 eventHelper.insertMDEvent(signal, errorSquared, 0, 0, pickDetectorID(),
249 eventCenter); // 0 = associated experiment-info index
250 }
251
252 ws->splitBox();
253 auto *ts = new ThreadSchedulerFIFO();
254 ThreadPool tp(ts);
255 ws->splitAllIfNeeded(ts);
256 tp.joinAll();
257 ws->refreshCache();
258}
259
264template <typename MDE, size_t nd> void FakeMD::addFakeUniformData(typename MDEventWorkspace<MDE, nd>::sptr ws) {
265 if (m_uniformParams.empty())
266 return;
267
268 bool randomEvents = true;
269 if (m_uniformParams[0] < 0) {
270 randomEvents = false;
272 }
273
274 if (m_uniformParams.size() == 1) {
275 if (randomEvents) {
276 for (size_t d = 0; d < nd; ++d) {
277 m_uniformParams.emplace_back(ws->getDimension(d)->getMinimum());
278 m_uniformParams.emplace_back(ws->getDimension(d)->getMaximum());
279 }
280 } else // regular events
281 {
282 auto nPoints = size_t(m_uniformParams[0]);
283 double Vol = 1;
284 for (size_t d = 0; d < nd; ++d)
285 Vol *= (ws->getDimension(d)->getMaximum() - ws->getDimension(d)->getMinimum());
286
287 if (Vol == 0 || Vol > std::numeric_limits<float>::max())
288 throw std::invalid_argument(" Domain ranges are not defined properly for workspace: " + ws->getName());
289
290 double dV = Vol / double(nPoints);
291 double delta0 = std::pow(dV, 1. / double(nd));
292 for (size_t d = 0; d < nd; ++d) {
293 double min = ws->getDimension(d)->getMinimum();
294 m_uniformParams.emplace_back(min * (1 + FLT_EPSILON) - min + FLT_EPSILON);
295 double extent = ws->getDimension(d)->getMaximum() - min;
296 auto nStrides = size_t(extent / delta0);
297 if (nStrides < 1)
298 nStrides = 1;
299 m_uniformParams.emplace_back(extent / static_cast<double>(nStrides));
300 }
301 }
302 }
303 if ((m_uniformParams.size() != 1 + nd * 2))
304 throw std::invalid_argument("UniformParams: needs to have ndims*2+1 arguments ");
305
306 if (randomEvents)
307 addFakeRandomData<MDE, nd>(m_uniformParams, ws);
308 else
309 addFakeRegularData<MDE, nd>(m_uniformParams, ws);
310
311 ws->splitBox();
312 auto *ts = new ThreadSchedulerFIFO();
313 ThreadPool tp(ts);
314 ws->splitAllIfNeeded(ts);
315 tp.joinAll();
316 ws->refreshCache();
317}
318
324template <typename MDE, size_t nd>
325void FakeMD::addFakeRandomData(const std::vector<double> &params, typename MDEventWorkspace<MDE, nd>::sptr ws) {
326
327 auto num = size_t(params[0]);
328 if (num == 0)
329 throw std::invalid_argument(" number of distributed events can not be equal to 0");
330
331 // Inserter to help choose the correct event type
333
334 // Array of distributions for each dimension
335 std::mt19937 rng(static_cast<unsigned int>(m_randomSeed));
336 std::array<std::uniform_real_distribution<double>, nd> gens;
337 for (size_t d = 0; d < nd; ++d) {
338 double min = params[d * 2 + 1];
339 double max = params[d * 2 + 2];
340 if (max <= min)
341 throw std::invalid_argument("UniformParams: min must be < max for all dimensions.");
342 gens[d] = std::uniform_real_distribution<double>(min, max);
343 }
344 // Unit-size randomizer
345 std::uniform_real_distribution<double> flat(0, 1.0); // Random from 0 to 1.0
346 // Create all the requested events
347 for (size_t i = 0; i < num; ++i) {
348 coord_t centers[nd];
349 for (size_t d = 0; d < nd; d++) {
350 centers[d] = static_cast<coord_t>(gens[d](rng));
351 }
352
353 // Default or randomized error/signal
354 float signal = 1.0;
355 float errorSquared = 1.0;
356 if (m_randomizeSignal) {
357 signal = float(0.5 + flat(rng));
358 errorSquared = float(0.5 + flat(rng));
359 }
360
361 // Create and add the event.
362 eventHelper.insertMDEvent(signal, errorSquared, 0, 0, pickDetectorID(),
363 centers); // 0 = associated experiment-info index
364 }
365}
366
367template <typename MDE, size_t nd>
368void FakeMD::addFakeRegularData(const std::vector<double> &params, typename MDEventWorkspace<MDE, nd>::sptr ws) {
369 // the parameters for regular distribution of events over the box
370 std::vector<double> startPoint(nd), delta(nd);
371 std::vector<size_t> indexMax(nd);
372 size_t gridSize(0);
373
374 auto num = size_t(params[0]);
375 if (num == 0)
376 throw std::invalid_argument(" number of distributed events can not be equal to 0");
377
378 // Inserter to help choose the correct event type
380
381 gridSize = 1;
382 for (size_t d = 0; d < nd; ++d) {
383 double min = ws->getDimension(d)->getMinimum();
384 double max = ws->getDimension(d)->getMaximum();
385 double shift = params[d * 2 + 1];
386 double step = params[d * 2 + 2];
387 if (shift < 0)
388 shift = 0;
389 if (shift >= step)
390 shift = step * (1 - FLT_EPSILON);
391
392 startPoint[d] = min + shift;
393 if ((startPoint[d] < min) || (startPoint[d] >= max))
394 throw std::invalid_argument("RegularData: starting point must be within "
395 "the box for all dimensions.");
396
397 if (step <= 0)
398 throw(std::invalid_argument("Step of the regular grid is less or equal to 0"));
399
400 indexMax[d] = size_t((max - min) / step);
401 if (indexMax[d] == 0)
402 indexMax[d] = 1;
403 // deal with round-off errors
404 while ((startPoint[d] + double(indexMax[d] - 1) * step) >= max)
405 step *= (1 - FLT_EPSILON);
406
407 delta[d] = step;
408
409 gridSize *= indexMax[d];
410 }
411 // Create all the requested events
412 size_t cellCount(0);
413 for (size_t i = 0; i < num; ++i) {
414 coord_t centers[nd];
415
416 auto indexes = Kernel::Utils::getIndicesFromLinearIndex(cellCount, indexMax);
417 ++cellCount;
418 if (cellCount >= gridSize)
419 cellCount = 0;
420
421 for (size_t d = 0; d < nd; d++) {
422 centers[d] = coord_t(startPoint[d] + delta[d] * double(indexes[d]));
423 }
424
425 // Default or randomized error/signal
426 float signal = 1.0;
427 float errorSquared = 1.0;
428
429 // Create and add the event.
430 eventHelper.insertMDEvent(signal, errorSquared, 0, 0, pickDetectorID(),
431 centers); // 0 = associated experiment-info index
432 }
433}
434
440 if (m_detIDs.empty()) {
441 return -1;
442 } else {
444 }
445}
446
447} // namespace Mantid::DataObjects
gsl_vector * tmp
static std::unique_ptr< QThreadPool > tp
IPeaksWorkspace_sptr workspace
Definition: IndexPeaks.cpp:114
#define CALL_MDEVENT_FUNCTION(funcname, workspace)
Macro that makes it possible to call a templated method for a MDEventWorkspace using a IMDEventWorksp...
double radius
Definition: Rasterize.cpp:31
Abstract base class for multi-dimension event workspaces (MDEventWorkspace).
virtual std::shared_ptr< const Mantid::Geometry::IMDDimension > getDimension(size_t index) const
Get a dimension.
Definition: MDGeometry.cpp:161
const std::string & getName() const override
Get the workspace name.
Definition: Workspace.cpp:58
std::mt19937 m_randGen
Definition: FakeMD.h:50
void fill(const API::IMDEventWorkspace_sptr &workspace)
Add the fake data to the given workspace.
Definition: FakeMD.cpp:53
void addFakeRandomData(const std::vector< double > &params, typename MDEventWorkspace< MDE, nd >::sptr ws)
Add fake randomized data to the workspace.
Definition: FakeMD.cpp:325
std::vector< detid_t > m_detIDs
Definition: FakeMD.h:49
const bool m_randomizeSignal
Definition: FakeMD.h:48
void addFakeUniformData(typename MDEventWorkspace< MDE, nd >::sptr ws)
Function makes up a fake uniform event data and adds it to the workspace.
Definition: FakeMD.cpp:264
void addFakeRegularData(const std::vector< double > &params, typename MDEventWorkspace< MDE, nd >::sptr ws)
Definition: FakeMD.cpp:368
FakeMD(const std::vector< double > &uniformParams, const std::vector< double > &peakParams, const std::vector< double > &ellipsoidParams, const int randomSeed, const bool randomizeSignal)
Constructor.
Definition: FakeMD.cpp:38
Kernel::uniform_int_distribution< size_t > m_uniformDist
Definition: FakeMD.h:51
detid_t pickDetectorID()
Pick a detector ID for a particular event.
Definition: FakeMD.cpp:439
void setupDetectorCache(const API::IMDEventWorkspace &workspace)
Setup a detector cache for randomly picking IDs from the first instrument in the ExperimentInfo list.
Definition: FakeMD.cpp:69
std::vector< double > m_peakParams
Definition: FakeMD.h:45
void addFakePeak(typename MDEventWorkspace< MDE, nd >::sptr ws)
Function generates random uniformly distributed events within an nD-sphere to simulate a single-cryst...
Definition: FakeMD.cpp:85
std::vector< double > m_ellipsoidParams
Definition: FakeMD.h:46
std::vector< double > m_uniformParams
Definition: FakeMD.h:44
void addFakeEllipsoid(typename MDEventWorkspace< MDE, nd >::sptr ws)
Function that adds a fake single-crystal peak with a multivariate normal distribution (an ellipsoid) ...
Definition: FakeMD.cpp:161
MDEventInserter : Helper class that provides a generic interface for adding events to an MDWorkspace ...
void splitBox() override
Split the top-level MDBox into a MDGridBox.
std::shared_ptr< MDEventWorkspace< MDE, nd > > sptr
Typedef for a shared pointer of this kind of event workspace.
void splitAllIfNeeded(Kernel::ThreadScheduler *ts) override
Split all boxes that exceed the split threshold.
void refreshCache() override
Refresh the cache (integrated signal of each box)
Numerical Matrix class.
Definition: Matrix.h:42
T Invert()
LU inversion routine.
Definition: Matrix.cpp:924
std::vector< T > getVector() const
Definition: Matrix.cpp:77
A Thread Pool implementation that keeps a certain number of threads running (normally,...
Definition: ThreadPool.h:36
A First-In-First-Out Thread Scheduler.
std::shared_ptr< IMDEventWorkspace > IMDEventWorkspace_sptr
Shared pointer to Mantid::API::IMDEventWorkspace.
std::size_t numEvents(::NeXus::File &file, bool &hasTotalCounts, bool &oldNeXusFileNames, const std::string &prefix, const NexusHDF5Descriptor &descriptor)
Get the number of events in the currently opened group.
void getIndicesFromLinearIndex(const size_t linear_index, size_t const *const numBins, const size_t numDims, size_t *const out_indices)
Convert an linear index in nDim workspace into vector of loop indexes of nDim depth loop Unsafe (poin...
Definition: Utils.h:228
float coord_t
Typedef for the data type to use for coordinate axes in MD objects such as MDBox, MDEventWorkspace,...
Definition: MDTypes.h:27
int32_t detid_t
Typedef for a detector ID.
Definition: SpectrumInfo.h:21