Mantid
Loading...
Searching...
No Matches
MaxEnt.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 +
10#include "MantidAPI/TextAxis.h"
20#include "MantidHistogramData/Histogram.h"
21#include "MantidHistogramData/HistogramBuilder.h"
22#include "MantidHistogramData/LinearGenerator.h"
28#include <algorithm>
29#include <gsl/gsl_linalg.h>
30#include <numeric>
31
32namespace Mantid::Algorithms {
33
34using Mantid::HistogramData::LinearGenerator;
35using Mantid::HistogramData::Points;
36
37using namespace API;
38using namespace Kernel;
39using namespace Mantid::DataObjects;
40using namespace Mantid::HistogramData;
41
42// Register the algorithm into the AlgorithmFactory
44
45namespace {
46
47// Maps defining the inverse caption and label for the reconstructed image
48// Example:
49// The input workspaces (X axis) is in (Time, s)
50// The output image should be in (Frequency, Hz)
51
52// Defines the new caption
53std::map<std::string, std::string> inverseCaption = {
54 {"Time", "Frequency"}, {"Frequency", "Time"}, {"d-Spacing", "q"}, {"q", "d-Spacing"}};
55// Defines the new label
56std::map<std::string, std::string> inverseLabel = {{"s", "Hz"},
57 {"microsecond", "MHz"},
58 {"Hz", "s"},
59 {"MHz", "microsecond"},
60 {"Angstrom", "Angstrom^-1"},
61 {"Angstrom^-1", "Angstrom"}};
62// A threshold for small singular values
63const double THRESHOLD = 1E-6;
64
65const double BIN_WIDTH_ERROR_LEVEL = 0.5;
66
74MatrixWorkspace_sptr removeZeros(MatrixWorkspace_sptr &ws, const std::vector<size_t> &itCount,
75 const std::string &yLabel) {
76
77 ws->setYUnitLabel(yLabel);
78 ws->getAxis(0)->unit() = UnitFactory::Instance().create("Label");
79 Unit_sptr unit = ws->getAxis(0)->unit();
80 std::shared_ptr<Units::Label> label = std::dynamic_pointer_cast<Units::Label>(unit);
81 label->setLabel("Number of Iterations", "");
82
83 const size_t nspec = ws->getNumberHistograms();
84 if (itCount.empty()) {
85 return ws; // In case, we don't have any spectra
86 }
87 for (size_t spec = 0; spec < nspec; spec++) {
88 auto &dataX = ws->dataX(spec);
89 dataX.resize(itCount[spec]);
90 auto &dataY = ws->dataY(spec);
91 dataY.resize(itCount[spec]);
92 auto &dataE = ws->dataE(spec);
93 dataE.resize(itCount[spec]);
94 }
95 return ws;
96}
97} // namespace
98
99//----------------------------------------------------------------------------------------------
100
102const std::string MaxEnt::name() const { return "MaxEnt"; }
103
105int MaxEnt::version() const { return 1; }
106
108const std::string MaxEnt::category() const { return "Arithmetic\\FFT"; }
109
111const std::string MaxEnt::summary() const {
112 return "Runs Maximum Entropy method on every spectrum of an input workspace. "
113 "It currently works for the case where data and image are related by a"
114 " 1D Fourier transform.";
115}
116
117//----------------------------------------------------------------------------------------------
121
122 // X values in input workspace must be (almost) equally spaced
124 std::make_unique<WorkspaceProperty<>>("InputWorkspace", "", Direction::Input,
125 std::make_shared<EqualBinSizesValidator>(BIN_WIDTH_ERROR_LEVEL)),
126 "An input workspace.");
127
128 declareProperty("ComplexData", false,
129 "If true, the input data is assumed to be complex and the "
130 "input workspace is expected to have an even number of "
131 "histograms (2N). Spectrum numbers S and S+N are assumed to "
132 "be the real and imaginary part of the complex signal "
133 "respectively.");
134
135 declareProperty("ComplexImage", true,
136 "If true, the algorithm will use complex images for the "
137 "calculations. This is the recommended option when there is "
138 "no prior knowledge about the image. If the image is known "
139 "to be real, this option can be set to false and the "
140 "algorithm will only consider the real part for "
141 "calculations.");
142
143 declareProperty("PositiveImage", false,
144 "If true, the reconstructed image is only allowed to take "
145 "positive values. It can take negative values otherwise. "
146 "This option defines the entropy formula that will be used "
147 "for the calculations (see next section for more details).");
148
149 declareProperty("AutoShift", false,
150 "Automatically calculate and apply phase shift. Zero on the "
151 "X axis is assumed to be in the first bin. If it is not, "
152 "setting this property will automatically correct for this.");
153
154 auto mustBePositive = std::make_shared<BoundedValidator<size_t>>();
155 mustBePositive->setLower(0);
156 declareProperty(std::make_unique<PropertyWithValue<size_t>>("ResolutionFactor", 1, mustBePositive, Direction::Input),
157 "An integer number indicating the factor by which the number "
158 "of points will be increased in the image and reconstructed "
159 "data");
160
161 auto mustBeNonNegative = std::make_shared<BoundedValidator<double>>();
162 mustBeNonNegative->setLower(1E-12);
163 declareProperty(std::make_unique<PropertyWithValue<double>>("A", 0.4, mustBeNonNegative, Direction::Input),
164 "A maximum entropy constant. This algorithm was first developed for the "
165 "ISIS muon group where the default 0.4 was found to give good "
166 "reconstructions. "
167 "In general the user will need to experiment with this value. Choosing a "
168 "small value may lead to unphysical spiky reconstructions and choosing "
169 "an increasingly large "
170 "value the reconstruction will start to resamble that of a direct "
171 "fourier "
172 "transform reconstruction. However, where the data contain a "
173 "zero Fourier data point with a small error the "
174 "reconstruction will be insensitive to the choice "
175 "of this property (and increasing so the more well determined "
176 "this data point is).");
177
179 std::make_unique<PropertyWithValue<double>>("ChiTargetOverN", 1.0, mustBeNonNegative, Direction::Input),
180 "Target value of Chi-square divided by the number of data points (N)");
181
182 declareProperty(std::make_unique<PropertyWithValue<double>>("ChiEps", 0.001, mustBeNonNegative, Direction::Input),
183 "Required precision for Chi-square");
184
186 std::make_unique<PropertyWithValue<double>>("DistancePenalty", 0.1, mustBeNonNegative, Direction::Input),
187 "Distance penalty applied to the current image at each iteration.");
188
189 declareProperty(std::make_unique<PropertyWithValue<double>>("MaxAngle", 0.001, mustBeNonNegative, Direction::Input),
190 "Maximum degree of non-parallelism between S (the entropy) and C "
191 "(chi-squared). These needs to be parallel. Chosing a smaller "
192 "shouldn't change the output. However, if you find this is the "
193 "case please let the Mantid team know since this indicates that "
194 "the default value of this proporty may need changing or "
195 "other changes to this implementation are required.");
196
197 mustBePositive = std::make_shared<BoundedValidator<size_t>>();
198 mustBePositive->setLower(1);
199 declareProperty(std::make_unique<PropertyWithValue<size_t>>("MaxIterations", 20000, mustBePositive, Direction::Input),
200 "Maximum number of iterations.");
201
203 std::make_unique<PropertyWithValue<size_t>>("AlphaChopIterations", 500, mustBePositive, Direction::Input),
204 "Maximum number of iterations in alpha chop.");
206 std::make_unique<WorkspaceProperty<>>("DataLinearAdj", "", Direction::Input, PropertyMode::Optional,
207 std::make_shared<EqualBinSizesValidator>(BIN_WIDTH_ERROR_LEVEL)),
208 "Adjusts the calculated data by multiplying each value by the "
209 "corresponding Y value of this workspace. "
210 "The data in this workspace is complex in the same manner as complex "
211 "input data.");
213 std::make_unique<WorkspaceProperty<>>("DataConstAdj", "", Direction::Input, PropertyMode::Optional,
214 std::make_shared<EqualBinSizesValidator>(BIN_WIDTH_ERROR_LEVEL)),
215 "Adjusts the calculated data by adding to each value the corresponding Y "
216 "value of this workspace. "
217 "If DataLinearAdj is also specified, this addition is done after its "
218 "multiplication. "
219 "See equation in documentation for how DataLinearAdj and DataConstAdj "
220 "are applied. "
221 "The data in this workspace is complex in the same manner as complex "
222 "input data.");
223 declareProperty("PerSpectrumReconstruction", true,
224 "Reconstruction is done independently on each spectrum. "
225 "If false, all the spectra use one image and the reconstructions "
226 "differ only through their adjustments. "
227 "ComplexData must be set true, when this is false.");
228
229 declareProperty(std::make_unique<WorkspaceProperty<>>("EvolChi", "", Direction::Output),
230 "Output workspace containing the evolution of Chi-sq.");
231 declareProperty(std::make_unique<WorkspaceProperty<>>("EvolAngle", "", Direction::Output),
232 "Output workspace containing the evolution of "
233 "non-paralellism between S and C.");
234 declareProperty(std::make_unique<WorkspaceProperty<>>("ReconstructedImage", "", Direction::Output),
235 "The output workspace containing the reconstructed image.");
236 declareProperty(std::make_unique<WorkspaceProperty<>>("ReconstructedData", "", Direction::Output),
237 "The output workspace containing the reconstructed data.");
238}
239
240//----------------------------------------------------------------------------------------------
243void MaxEnt::validateBinEdges(const std::string &wsName, std::map<std::string, std::string> &messages) {
244 const double warningLevel = 0.01;
246 if (ws) {
247
248 Kernel::EqualBinsChecker binChecker(ws->readX(0), BIN_WIDTH_ERROR_LEVEL, warningLevel);
249 const std::string binError = binChecker.validate();
250 if (!binError.empty()) {
251 messages[wsName] = binError;
252 }
253 }
254}
255
256//----------------------------------------------------------------------------------------------
259std::map<std::string, std::string> MaxEnt::validateInputs() {
260
261 std::map<std::string, std::string> result;
262
263 validateBinEdges("InputWorkspace", result);
264 validateBinEdges("DataLinearAdj", result);
265 validateBinEdges("DataConstAdj", result);
266
267 MatrixWorkspace_sptr inWS = getProperty("InputWorkspace");
268
269 size_t nHistograms = 0;
270 if (inWS) {
271 // If the input signal is complex, we expect an even number of histograms
272 // in the input workspace
273
274 nHistograms = inWS->getNumberHistograms();
275 bool complex = getProperty("ComplexData");
276 if (complex && (nHistograms % 2))
277 result["InputWorkspace"] = "The number of histograms in the input "
278 "workspace must be even for complex data";
279 if (!complex)
280 nHistograms *= 2; // Double number of real histograms to compare with
281 // adjustments, which are always complex.
282 }
283
284 // Check linear adjustments, we expect and even number of histograms
285 // and if any, they must be sufficient for all spectra in input workspace,
286 // if per spectrum reconstruction is done.
287 MatrixWorkspace_sptr linAdj = getProperty("DataLinearAdj");
288 size_t nAHistograms = 0;
289 if (linAdj)
290 nAHistograms = linAdj->getNumberHistograms();
291 if (nAHistograms % 2)
292 result["DataLinearAdj"] = "The number of histograms in the linear "
293 "adjustments workspace must be even, because they are complex data";
294 else if (nAHistograms > 0 && nAHistograms < nHistograms)
295 result["DataLinearAdj"] = "The number of histograms in the linear "
296 "adjustments workspace is insufficient for the input workspace";
297
298 // Check constant adjustments, we expect and even number of histograms
299 // and if any, they must be sufficient for all spectra in input workspace,
300 // if per spectrum reconstruction is done.
301 MatrixWorkspace_sptr constAdj = getProperty("DataConstAdj");
302 nAHistograms = 0;
303 if (constAdj)
304 nAHistograms = constAdj->getNumberHistograms();
305 if (nAHistograms % 2)
306 result["DataConstAdj"] = "The number of histograms in the constant "
307 "adjustments workspace must be even, because they are complex data";
308 else if (nAHistograms > 0 && nAHistograms < nHistograms)
309 result["DataConstAdj"] = "The number of histograms in the constant "
310 "adjustments workspace is insufficient for the input workspace";
311
312 return result;
313}
314
315//----------------------------------------------------------------------------------------------
319
320 // MaxEnt parameters
321 // Complex data?
322 const bool complexData = getProperty("ComplexData");
323 // Complex image?
324 const bool complexImage = getProperty("ComplexImage");
325 // Image must be positive?
326 const bool positiveImage = getProperty("PositiveImage");
327 // Autoshift
328 const bool autoShift = getProperty("AutoShift");
329 // Increase the number of points in the image by this factor
330 const size_t resolutionFactor = getProperty("ResolutionFactor");
331 // Background (default level, sky background, etc)
332 const double background = getProperty("A");
333 // Chi target
334 const double ChiTargetOverN = getProperty("ChiTargetOverN");
335 // Required precision for Chi arget
336 const double chiEps = getProperty("ChiEps");
337 // Maximum degree of non-parallelism between S and C
338 const double angle = getProperty("MaxAngle");
339 // Distance penalty for current image
340 const double distEps = getProperty("DistancePenalty");
341 // Maximum number of iterations
342 const size_t nIter = getProperty("MaxIterations");
343 // Maximum number of iterations in alpha chop
344 const size_t alphaIter = getProperty("AlphaChopIterations");
345 // Number of spectra and datapoints
346 // Read input workspace
347 MatrixWorkspace_const_sptr inWS = getProperty("InputWorkspace");
348 // Number of spectra
349 size_t nHist = inWS->getNumberHistograms();
350 // Number of data points - assumed to be constant between spectra or
351 // this will throw an exception
352 size_t npoints = inWS->blocksize() * resolutionFactor;
353 // Number of X bins
354 const size_t npointsX = inWS->isHistogramData() ? npoints + 1 : npoints;
355 // Linear adjustment of calculated data
356 MatrixWorkspace_const_sptr dataLinearAdj = getProperty("DataLinearAdj");
357 // Constant adjustment of calculated data
358 MatrixWorkspace_const_sptr dataConstAdj = getProperty("DataConstAdj");
359 // Add spectra in reconstruction if false
360 const bool perSpectrumReconstruction = getProperty("PerSpectrumReconstruction");
361
362 // For now have the requirement that data must have non-zero
363 // (and positive!) errors
364 for (size_t s = 0; s < nHist; s++) {
365 const auto &errors = inWS->e(s);
366 if (std::any_of(errors.cbegin(), errors.cend(), [](const auto error) { return error <= 0.; })) {
367 throw std::invalid_argument("Input data must have all errors non-zero.");
368 }
369 }
370
371 // Is our data space real or complex?
372 MaxentSpace_sptr dataSpace;
373 if (complexData) {
374 dataSpace = std::make_shared<MaxentSpaceComplex>();
375 } else {
376 dataSpace = std::make_shared<MaxentSpaceReal>();
377 }
378 // Is our image space real or complex?
379 MaxentSpace_sptr imageSpace;
380 if (complexImage) {
381 imageSpace = std::make_shared<MaxentSpaceComplex>();
382 } else {
383 imageSpace = std::make_shared<MaxentSpaceReal>();
384 }
385 // The type of transform. Currently a 1D Fourier Transform or Multiple ID
386 // Fourier transform
387 MaxentTransform_sptr transform;
388 if (perSpectrumReconstruction) {
389 transform = std::make_shared<MaxentTransformFourier>(dataSpace, imageSpace);
390 } else {
391 auto complexDataSpace = std::make_shared<MaxentSpaceComplex>();
392 transform = std::make_shared<MaxentTransformMultiFourier>(complexDataSpace, imageSpace, nHist / 2);
393 }
394
395 // The type of entropy we are going to use (depends on the type of image,
396 // positive only, or positive and/or negative)
397 MaxentEntropy_sptr entropy;
398 if (positiveImage) {
399 entropy = std::make_shared<MaxentEntropyPositiveValues>();
400 } else {
401 entropy = std::make_shared<MaxentEntropyNegativeValues>();
402 }
403
404 // Entropy and transform is all we need to set up a calculator
405 MaxentCalculator maxentCalculator = MaxentCalculator(entropy, transform);
406
407 // Output workspaces
408 MatrixWorkspace_sptr outImageWS;
409 MatrixWorkspace_sptr outDataWS;
410 MatrixWorkspace_sptr outEvolChi;
411 MatrixWorkspace_sptr outEvolTest;
412
413 size_t nDataSpec = complexData ? nHist / 2 : nHist;
414 size_t nImageSpec = nDataSpec;
415 size_t nSpecConcat = 1;
416 if (!perSpectrumReconstruction) {
417 nSpecConcat = nImageSpec;
418 nImageSpec = 1;
419 }
420 outImageWS = create<MatrixWorkspace>(*inWS, 2 * nImageSpec, Points(npoints));
421 for (size_t i = 0; i < outImageWS->getNumberHistograms(); ++i)
422 outImageWS->getSpectrum(i).setDetectorID(static_cast<detid_t>(i + 1));
423 HistogramBuilder builder;
424 builder.setX(npointsX);
425 builder.setY(npoints);
426 builder.setDistribution(inWS->isDistribution());
427 outDataWS = create<MatrixWorkspace>(*inWS, 2 * nDataSpec, builder.build());
428
429 for (size_t i = 0; i < outDataWS->getNumberHistograms(); ++i)
430 outDataWS->getSpectrum(i).setDetectorID(static_cast<detid_t>(i + 1));
431 outEvolChi = create<MatrixWorkspace>(*inWS, nImageSpec, Points(nIter));
432 outEvolTest = create<MatrixWorkspace>(*inWS, nImageSpec, Points(nIter));
433
434 npoints = complexImage ? npoints * 2 : npoints;
435 std::vector<size_t> iterationCounts;
436 iterationCounts.reserve(nImageSpec);
437 outEvolChi->setPoints(0, Points(nIter, LinearGenerator(0.0, 1.0)));
438
439 size_t dataLength = complexData ? 2 * inWS->y(0).size() : inWS->y(0).size();
440 dataLength *= nSpecConcat;
441
442 for (size_t spec = 0; spec < nImageSpec; spec++) {
443
444 // Start distribution (flat background)
445 std::vector<double> image(npoints, background);
446
447 std::vector<double> data(dataLength, 0.0);
448 std::vector<double> errors(dataLength, 0.0);
449 if (complexData) {
450 data = toComplex(inWS, spec, false,
451 !perSpectrumReconstruction); // 3rd arg false -> data
452 errors = toComplex(inWS, spec, true,
453 !perSpectrumReconstruction); // 3rd arg true -> errors
454 } else {
455 if (!perSpectrumReconstruction) {
456 throw std::invalid_argument("ComplexData must be true, if PerSpectrumReconstruction is false.");
457 } else {
458 data = inWS->y(spec).rawData();
459 errors = inWS->e(spec).rawData();
460 }
461 }
462
463 std::vector<double> linearAdjustments;
464 std::vector<double> constAdjustments;
465 if (dataLinearAdj) {
466 linearAdjustments = toComplex(dataLinearAdj, spec, false, !perSpectrumReconstruction);
467 }
468 if (dataConstAdj) {
469 constAdjustments = toComplex(dataConstAdj, spec, false, !perSpectrumReconstruction);
470 }
471
472 // To record the algorithm's progress
473 std::vector<double> evolChi(nIter, 0.);
474 std::vector<double> evolTest(nIter, 0.);
475
476 // Progress
477 Progress progress(this, 0.0, 1.0, nIter);
478
479 // Run maxent algorithm
480 bool converged = false;
481 for (size_t it = 0; it < nIter; it++) {
482
483 // Iterates one step towards the solution. This means calculating
484 // quadratic coefficients, search directions, angle and chi-sq
485 maxentCalculator.iterate(data, errors, image, background, linearAdjustments, constAdjustments);
486
487 // Calculate delta to construct new image (SB eq. 25)
488 double currChisq = maxentCalculator.getChisq();
489 auto coeffs = maxentCalculator.getQuadraticCoefficients();
490 auto delta = move(coeffs, ChiTargetOverN / currChisq, chiEps, alphaIter);
491
492 // Apply distance penalty (SB eq. 33)
493 delta = applyDistancePenalty(delta, coeffs, image, background, distEps);
494
495 // Update image
496 auto dirs = maxentCalculator.getSearchDirections();
497 image = updateImage(image, delta, dirs);
498
499 // Record the evolution of Chi-square and angle(S,C)
500 double currAngle = maxentCalculator.getAngle();
501 evolChi[it] = currChisq;
502 evolTest[it] = currAngle;
503
504 // Stop condition for convergence, solution found
505 if ((std::abs(currChisq / ChiTargetOverN - 1.) < chiEps) && (currAngle < angle)) {
506
507 // it + 1 iterations have been done because we count from zero
508 g_log.information() << "Converged after " << it + 1 << " iterations" << std::endl;
509 iterationCounts.emplace_back(it + 1);
510 converged = true;
511 break;
512 }
513
514 // Check for canceling the algorithm
515 if (!(it % 1000)) {
517 }
518
519 progress.report();
520
521 } // Next Iteration
522
523 // If we didn't converge, we still need to record the number of iterations
524 if (!converged) {
525 iterationCounts.emplace_back(nIter);
526 }
527
528 // Get calculated data
529 auto solData = maxentCalculator.getReconstructedData();
530 auto solImage = maxentCalculator.getImage();
531
532 // Populate the output workspaces
533 populateDataWS(inWS, spec, nDataSpec, solData, !perSpectrumReconstruction, complexData, outDataWS);
534 populateImageWS(inWS, spec, nImageSpec, solImage, complexImage, outImageWS, autoShift);
535
536 // Populate workspaces recording the evolution of Chi and Test
537 // X values
538 outEvolChi->setSharedX(spec, outEvolChi->sharedX(0));
539 outEvolTest->setSharedX(spec, outEvolChi->sharedX(0));
540
541 // Y values
542 outEvolChi->setCounts(spec, std::move(evolChi));
543 outEvolTest->setCounts(spec, std::move(evolTest));
544 // No errors
545
546 } // Next spectrum
547 setProperty("EvolChi", removeZeros(outEvolChi, iterationCounts, "Chi squared"));
548 setProperty("EvolAngle", removeZeros(outEvolTest, iterationCounts, "Maximum Angle"));
549 setProperty("ReconstructedImage", outImageWS);
550 setProperty("ReconstructedData", outDataWS);
551}
552
553//----------------------------------------------------------------------------------------------
554
564std::vector<double> MaxEnt::toComplex(API::MatrixWorkspace_const_sptr &inWS, size_t spec, bool errors,
565 bool concatSpec) {
566 const size_t numBins = inWS->y(0).size();
567 size_t nSpec = inWS->getNumberHistograms() / 2;
568 std::vector<double> result;
569 result.reserve(2 * numBins);
570
571 if (inWS->getNumberHistograms() % 2)
572 throw std::invalid_argument("Cannot convert input workspace to complex data");
573
574 size_t nSpecOfInterest = (concatSpec ? nSpec : 1);
575 size_t firstSpecOfInterest = (concatSpec ? 0 : spec);
576
577 for (size_t s = firstSpecOfInterest; s < firstSpecOfInterest + nSpecOfInterest; s++) {
578 if (!errors) {
579 for (size_t i = 0; i < numBins; i++) {
580 result.emplace_back(inWS->y(s)[i]);
581 result.emplace_back(inWS->y(s + nSpec)[i]);
582 }
583 } else {
584 for (size_t i = 0; i < numBins; i++) {
585 result.emplace_back(inWS->e(s)[i]);
586 result.emplace_back(inWS->e(s + nSpec)[i]);
587 }
588 }
589 }
590
591 return result;
592}
593
603std::vector<double> MaxEnt::move(const QuadraticCoefficients &coeffs, double ChiTargetOverN, double chiEps,
604 size_t alphaIter) {
605
606 double aMin = 0.; // Minimum alpha
607 double aMax = 1.; // Maximum alpha
608
609 // Dimension, number of search directions
610 size_t dim = coeffs.c2.size().first;
611
612 std::vector<double> deltaMin(dim, 0); // delta at alpha min
613 std::vector<double> deltaMax(dim, 0); // delta at alpha max
614
615 double chiMin = calculateChi(coeffs, aMin, deltaMin); // Chi at alpha min
616 double chiMax = calculateChi(coeffs, aMax, deltaMax); // Chi at alpha max
617
618 double dchiMin = chiMin - ChiTargetOverN; // max - target
619 double dchiMax = chiMax - ChiTargetOverN; // min - target
620
621 if (dchiMin * dchiMax > 0) {
622 // ChiTargetOverN could be outside the range [chiMin, chiMax]
623
624 if (fabs(dchiMin) < fabs(dchiMax)) {
625 return deltaMin;
626 } else {
627 return deltaMax;
628 }
629 // throw std::runtime_error("Error in alpha chop\n");
630 }
631
632 // Initial values of eps and iter to start while loop
633 double eps = 2. * chiEps;
634 size_t iter = 0;
635
636 // Bisection method
637
638 std::vector<double> delta(dim, 0); // delta at current alpha
639
640 while ((fabs(eps / ChiTargetOverN) > chiEps) && (iter < alphaIter)) {
641
642 double aMid = 0.5 * (aMin + aMax);
643 double chiMid = calculateChi(coeffs, aMid, delta);
644
645 eps = chiMid - ChiTargetOverN;
646
647 if (dchiMin * eps > 0) {
648 aMin = aMid;
649 dchiMin = eps;
650 }
651
652 if (dchiMax * eps > 0) {
653 aMax = aMid;
654 dchiMax = eps;
655 }
656
657 iter++;
658 }
659
660 // Check if move was successful
661 if ((fabs(eps / ChiTargetOverN) > chiEps) || (iter > alphaIter)) {
662 throw std::runtime_error("Error encountered when calculating solution "
663 "image. No convergence in alpha chop.\n");
664 }
665
666 return delta;
667}
668
676double MaxEnt::calculateChi(const QuadraticCoefficients &coeffs, double a, std::vector<double> &b) {
677
678 size_t dim = coeffs.c2.size().first;
679
680 double ax = a;
681 double bx = 1 - ax;
682
683 Kernel::DblMatrix A(dim, dim);
684 Kernel::DblMatrix B(dim, 1);
685
686 // Construct the matrix A and vector B such that Ax=B
687 for (size_t k = 0; k < dim; k++) {
688 for (size_t l = 0; l < dim; l++) {
689 A[k][l] = bx * coeffs.c2[k][l] - ax * coeffs.s2[k][l];
690 }
691 B[k][0] = -bx * coeffs.c1[k][0] + ax * coeffs.s1[k][0];
692 }
693
694 // Alternatives I have tried:
695 // Gauss-Jordan
696 // LU
697 // SVD seems to work better
698
699 // Solve using SVD
700 b = solveSVD(A, B);
701
702 // Now compute Chi
703 double ww = 0.;
704 for (size_t k = 0; k < dim; k++) {
705 double z = 0.;
706 for (size_t l = 0; l < dim; l++) {
707 z += coeffs.c2[k][l] * b[l];
708 }
709 ww += b[k] * (coeffs.c1[k][0] + 0.5 * z);
710 }
711
712 // Return chi
713 return ww + 1.;
714}
715
721std::vector<double> MaxEnt::solveSVD(DblMatrix &A, const DblMatrix &B) {
722
723 size_t dim = A.size().first;
724
725 auto a = gsl_matrix_view_array(A[0], dim, dim);
726 auto b = gsl_vector_const_view_array(B[0], dim);
727
728 std::vector<double> vVec(dim * dim), sVec(dim), wVec(dim), delta(dim);
729
730 auto v = gsl_matrix_view_array(vVec.data(), dim, dim);
731 auto s = gsl_vector_view_array(sVec.data(), dim);
732 auto w = gsl_vector_view_array(wVec.data(), dim);
733 auto x = gsl_vector_view_array(delta.data(), dim);
734
735 // Singular value decomposition
736 gsl_linalg_SV_decomp(&a.matrix, &v.matrix, &s.vector, &w.vector);
737
738 // A could be singular or ill-conditioned. We can use SVD to obtain a least
739 // squares solution by setting the small (compared to the maximum) singular
740 // values to zero
741
742 // Find largest sing value
743 double max = *std::max_element(sVec.begin(), sVec.end());
744
745 // Apply a threshold to small singular values
746 double threshold = THRESHOLD * max;
747 std::transform(sVec.begin(), sVec.end(), sVec.begin(), [&threshold](double el) { return el > threshold ? el : 0.0; });
748
749 // Solve A*x = B
750 gsl_linalg_SV_solve(&a.matrix, &v.matrix, &s.vector, &b.vector, &x.vector);
751
752 return delta;
753}
754
763std::vector<double> MaxEnt::applyDistancePenalty(const std::vector<double> &delta, const QuadraticCoefficients &coeffs,
764 const std::vector<double> &image, double background, double distEps) {
765
766 const double pointSum = std::accumulate(image.cbegin(), image.cend(), 0.,
767 [](const auto sum, const auto point) { return sum + std::abs(point); });
768
769 const size_t dim = coeffs.s2.size().first;
770
771 double dist = 0.;
772
773 for (size_t k = 0; k < dim; k++) {
774 double sum = 0.0;
775 for (size_t l = 0; l < dim; l++)
776 sum -= coeffs.s2[k][l] * delta[l];
777 dist += delta[k] * sum;
778 }
779
780 if (dist > distEps * pointSum / background) {
781 auto newDelta = delta;
782 for (size_t k = 0; k < delta.size(); k++) {
783 newDelta[k] *= sqrt(distEps * pointSum / dist / background);
784 }
785 return newDelta;
786 }
787 return delta;
788}
789
798std::vector<double> MaxEnt::updateImage(const std::vector<double> &image, const std::vector<double> &delta,
799 const std::vector<std::vector<double>> &dirs) {
800
801 if (image.empty() || dirs.empty() || (delta.size() != dirs.size())) {
802 throw std::runtime_error("Cannot calculate new image");
803 }
804
805 std::vector<double> newImage = image;
806
807 // Calculate the new image
808 for (size_t i = 0; i < image.size(); i++) {
809 for (size_t k = 0; k < delta.size(); k++) {
810 newImage[i] += delta[k] * dirs[k][i];
811 }
812 }
813 return newImage;
814}
815
827void MaxEnt::populateImageWS(MatrixWorkspace_const_sptr &inWS, size_t spec, size_t nspec,
828 const std::vector<double> &result, bool complex, MatrixWorkspace_sptr &outWS,
829 bool autoShift) {
830
831 if (complex && result.size() % 2)
832 throw std::invalid_argument("Cannot write image results to output workspaces");
833
834 int npoints = complex ? static_cast<int>(result.size() / 2) : static_cast<int>(result.size());
835 MantidVec X(npoints);
836 MantidVec YR(npoints);
837 MantidVec YI(npoints);
838 MantidVec E(npoints, 0.);
839
840 auto dataPoints = inWS->points(spec);
841 double x0 = dataPoints[0];
842 double dx = dataPoints[1] - x0;
843
844 double delta = 1. / dx / npoints;
845 const int isOdd = (inWS->y(0).size() % 2) ? 1 : 0;
846
847 double shift = x0 * 2. * M_PI;
848 if (!autoShift)
849 shift = 0.;
850
851 // X values
852 for (int i = 0; i < npoints; i++) {
853 X[i] = delta * (-npoints / 2 + i);
854 }
855
856 // Y values
857 if (complex) {
858 for (int i = 0; i < npoints; i++) {
859 int j = (npoints / 2 + i + isOdd) % npoints;
860 double xShift = X[i] * shift;
861 double c = cos(xShift);
862 double s = sin(xShift);
863 YR[i] = result[2 * j] * c - result[2 * j + 1] * s;
864 YI[i] = result[2 * j] * s + result[2 * j + 1] * c;
865 YR[i] *= dx;
866 YI[i] *= dx;
867 }
868 } else {
869 for (int i = 0; i < npoints; i++) {
870 int j = (npoints / 2 + i + isOdd) % npoints;
871 double xShift = X[i] * shift;
872 double c = cos(xShift);
873 double s = sin(xShift);
874 YR[i] = result[j] * c;
875 YI[i] = result[j] * s;
876 YR[i] *= dx;
877 YI[i] *= dx;
878 }
879 }
880
881 // X caption & label
882 auto inputUnit = inWS->getAxis(0)->unit();
883 if (inputUnit) {
884 std::shared_ptr<Kernel::Units::Label> lblUnit =
885 std::dynamic_pointer_cast<Kernel::Units::Label>(UnitFactory::Instance().create("Label"));
886 if (lblUnit) {
887
888 lblUnit->setLabel(inverseCaption[inWS->getAxis(0)->unit()->caption()],
889 inverseLabel[inWS->getAxis(0)->unit()->label().ascii()]);
890 outWS->getAxis(0)->unit() = lblUnit;
891 }
892 }
893
894 outWS->mutableX(spec) = X;
895 outWS->mutableY(spec) = YR;
896 outWS->mutableE(spec) = E;
897 outWS->setSharedX(nspec + spec, outWS->sharedX(spec));
898 outWS->mutableY(nspec + spec) = YI;
899 outWS->setSharedE(nspec + spec, outWS->sharedE(spec));
900}
901
913void MaxEnt::populateDataWS(MatrixWorkspace_const_sptr &inWS, size_t spec, size_t nspec,
914 const std::vector<double> &result, bool concatenated, bool complex,
915 MatrixWorkspace_sptr &outWS) {
916
917 if (complex && result.size() % 2)
918 throw std::invalid_argument("Cannot write data results to output workspaces");
919 if (concatenated && !complex)
920 throw std::invalid_argument("Concatenated data results must be complex");
921 if (concatenated && result.size() % (nspec * 2))
922 throw std::invalid_argument("Cannot write complex concatenated data results to output workspaces");
923 if (concatenated && spec != 0)
924 throw std::invalid_argument("Cannot write concatenated data results to "
925 "output workspaces from non-first spectrum");
926
927 int resultLength = complex ? static_cast<int>(result.size() / 2) : static_cast<int>(result.size());
928 size_t spectrumLength = (concatenated ? resultLength / nspec : resultLength);
929 size_t spectrumLengthX = inWS->isHistogramData() ? spectrumLength + 1 : spectrumLength;
930 size_t nSpecAnalyzed = (concatenated ? nspec : 1);
931
932 // Here we assume equal constant binning for all spectra analyzed
933 double x0 = inWS->x(spec)[0];
934 double dx = inWS->x(spec)[1] - x0;
935
936 // Loop over each spectrum being analyzed - one spectrum unless concatenated
937 for (size_t specA = spec; specA < spec + nSpecAnalyzed; specA++) {
938
939 MantidVec X(spectrumLengthX);
940 MantidVec YR(spectrumLength);
941 MantidVec YI(spectrumLength);
942 MantidVec E(spectrumLength, 0.);
943
944 // X values
945 for (size_t i = 0; i < spectrumLengthX; i++) {
946 X[i] = x0 + static_cast<double>(i) * dx;
947 }
948
949 // Y values
950 if (complex) {
951 if (concatenated) {
952 // note the spec=0, so specA starts from 0 in this case.
953 for (size_t i = 0; i < spectrumLength; i++) {
954 YR[i] = result[2 * i + 2 * specA * spectrumLength];
955 YI[i] = result[2 * i + 1 + 2 * specA * spectrumLength];
956 }
957 } else {
958 for (size_t i = 0; i < spectrumLength; i++) {
959 YR[i] = result[2 * i];
960 YI[i] = result[2 * i + 1];
961 }
962 }
963 } else {
964 for (size_t i = 0; i < spectrumLength; i++) {
965 YR[i] = result[i];
966 YI[i] = 0.;
967 }
968 }
969
970 outWS->mutableX(specA) = X;
971 outWS->mutableY(specA) = YR;
972 outWS->mutableE(specA) = E;
973 outWS->mutableY(nspec + specA) = YI;
974 outWS->setSharedX(nspec + specA, outWS->sharedX(spec));
975 outWS->setSharedE(nspec + specA, outWS->sharedE(spec));
976 } // Next spectrum if concatenated
977}
978
979} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition: Algorithm.h:576
double error
Definition: IndexPeaks.cpp:133
#define fabs(x)
Definition: Matrix.cpp:22
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
Definition: Algorithm.cpp:1913
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Definition: Algorithm.cpp:2076
Kernel::Logger & g_log
Definition: Algorithm.h:451
void progress(double p, const std::string &msg="", double estimatedTime=0.0, int progressPrecision=0)
Sends ProgressNotification.
Definition: Algorithm.cpp:231
void interruption_point()
This is called during long-running operations, and check if the algorithm has requested that it be ca...
Definition: Algorithm.cpp:1687
Helper class for reporting progress from algorithms.
Definition: Progress.h:25
A property class for workspaces.
void populateImageWS(API::MatrixWorkspace_const_sptr &inWS, size_t spec, size_t nspec, const std::vector< double > &result, bool complex, API::MatrixWorkspace_sptr &outWS, bool autoShift)
Populates the output workspace containing the reconstructed image.
Definition: MaxEnt.cpp:827
std::map< std::string, std::string > validateInputs() override
Validate the input properties.
Definition: MaxEnt.cpp:259
int version() const override
Algorithm's version.
Definition: MaxEnt.cpp:105
std::vector< double > solveSVD(Kernel::DblMatrix &A, const Kernel::DblMatrix &B)
Solves A*x = B using SVD.
Definition: MaxEnt.cpp:721
std::vector< double > updateImage(const std::vector< double > &image, const std::vector< double > &delta, const std::vector< std::vector< double > > &dirs)
Updates the image.
Definition: MaxEnt.cpp:798
std::vector< double > toComplex(API::MatrixWorkspace_const_sptr &inWS, size_t spec, bool errors, bool concatenatedSpectra)
Returns spectrum 'spec' as a complex vector.
Definition: MaxEnt.cpp:564
const std::string category() const override
Algorithm's category.
Definition: MaxEnt.cpp:108
double calculateChi(const QuadraticCoefficients &coeffs, double a, std::vector< double > &beta)
Calculates Chi given the quadratic coefficients and an alpha value by solving the matrix equation A*b...
Definition: MaxEnt.cpp:676
void validateBinEdges(const std::string &wsName, std::map< std::string, std::string > &messages)
Checks the bin spacing is equal.
Definition: MaxEnt.cpp:243
void init() override
Initialise the algorithm's properties.
Definition: MaxEnt.cpp:120
const std::string summary() const override
Algorithm's summary.
Definition: MaxEnt.cpp:111
std::vector< double > applyDistancePenalty(const std::vector< double > &beta, const QuadraticCoefficients &coeffs, const std::vector< double > &image, double background, double distEps)
Applies a distance penalty.
Definition: MaxEnt.cpp:763
const std::string name() const override
Algorithm's name.
Definition: MaxEnt.cpp:102
void populateDataWS(API::MatrixWorkspace_const_sptr &inWS, size_t spec, size_t nspec, const std::vector< double > &result, bool concatenatedSpectra, bool complex, API::MatrixWorkspace_sptr &outWS)
Populates the output workspace containing the reconstructed data.
Definition: MaxEnt.cpp:913
void exec() override
Run the algorithm.
Definition: MaxEnt.cpp:318
std::vector< double > move(const QuadraticCoefficients &coeffs, double ChiTargetOverN, double chiEps, size_t alphaIter)
Moves the system one step closer towards the solution.
Definition: MaxEnt.cpp:603
MaxentCalculator : This class performs one maxent iteration and calculates chi-sq,...
std::vector< std::vector< double > > getSearchDirections() const
Returns the search directions (in image space)
double getAngle() const
Returns the angle between the gradient of chi-square and the gradient of the entropy (calculated and ...
std::vector< double > getReconstructedData() const
Returns the reconstructed (calculated) data.
QuadraticCoefficients getQuadraticCoefficients() const
Returns the quadratic coefficients.
void iterate(const std::vector< double > &data, const std::vector< double > &errors, const std::vector< double > &image, double background, const std::vector< double > &linearAdjustments, const std::vector< double > &constAdjustments)
Performs an iteration and calculates everything: search directions (SB.
double getChisq()
Returns chi-square.
std::vector< double > getImage() const
Returns the (reconstructed) image.
EqualBinsChecker : Checks for evenly spaced bins.
virtual std::string validate() const
Perform validation of the given X array.
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
void information(const std::string &msg)
Logs at information level.
Definition: Logger.cpp:105
std::pair< size_t, size_t > size() const
Access matrix sizes.
Definition: Matrix.h:141
The concrete, templated class for properties.
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
std::shared_ptr< const MatrixWorkspace > MatrixWorkspace_const_sptr
shared pointer to the matrix workspace base class (const version)
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
std::shared_ptr< MaxentTransform > MaxentTransform_sptr
std::shared_ptr< MaxentEntropy > MaxentEntropy_sptr
Definition: MaxentEntropy.h:33
std::shared_ptr< MaxentSpace > MaxentSpace_sptr
Definition: MaxentSpace.h:33
std::unique_ptr< T > create(const P &parent, const IndexArg &indexArg, const HistArg &histArg)
This is the create() method that all the other create() methods call.
std::shared_ptr< Unit > Unit_sptr
Shared pointer to the Unit base class.
Definition: Unit.h:229
int32_t detid_t
Typedef for a detector ID.
Definition: SpectrumInfo.h:21
std::vector< double > MantidVec
typedef for the data storage used in Mantid matrix workspaces
Definition: cow_ptr.h:172
@ Input
An input workspace.
Definition: Property.h:53
@ Output
An output workspace.
Definition: Property.h:54
Mantid::Kernel::DblMatrix s2
Mantid::Kernel::DblMatrix s1
Mantid::Kernel::DblMatrix c1
Mantid::Kernel::DblMatrix c2