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