/** * Sum counts from the input workspace in lambda along lines of constant Q by * projecting to "virtual lambda" at a reference angle. * * @param detectorWS [in] :: the input workspace in wavelength * @param indices [in] :: an index set defining the foreground histograms * @return :: the single histogram output workspace in wavelength */ API::MatrixWorkspace_sptr ReflectometrySumInQ::sumInQ(const API::MatrixWorkspace &detectorWS, const Indexing::SpectrumIndexSet &indices) { const auto spectrumInfo = detectorWS.spectrumInfo(); const auto refAngles = referenceAngles(spectrumInfo); // Construct the output workspace in virtual lambda API::MatrixWorkspace_sptr IvsLam = constructIvsLamWS(detectorWS, indices, refAngles); auto &outputE = IvsLam->dataE(0); // Loop through each spectrum in the detector group for (auto spIdx : indices) { if (spectrumInfo.isMasked(spIdx) || spectrumInfo.isMonitor(spIdx)) { continue; } // Get the size of this detector in twoTheta const auto twoThetaRange = twoThetaWidth(spIdx, spectrumInfo); // Check X length is Y length + 1 const auto inputBinEdges = detectorWS.binEdges(spIdx); const auto inputCounts = detectorWS.counts(spIdx); const auto inputStdDevs = detectorWS.countStandardDeviations(spIdx); // Create a vector for the projected errors for this spectrum. // (Output Y values can simply be accumulated directly into the output // workspace, but for error values we need to create a separate error // vector for the projected errors from each input spectrum and then // do an overall sum in quadrature.) std::vector<double> projectedE(outputE.size(), 0.0); // Process each value in the spectrum const int ySize = static_cast<int>(inputCounts.size()); for (int inputIdx = 0; inputIdx < ySize; ++inputIdx) { // Do the summation in Q processValue(inputIdx, twoThetaRange, refAngles, inputBinEdges, inputCounts, inputStdDevs, *IvsLam, projectedE); } // Sum errors in quadrature const int eSize = static_cast<int>(outputE.size()); for (int outIdx = 0; outIdx < eSize; ++outIdx) { outputE[outIdx] += projectedE[outIdx] * projectedE[outIdx]; } } // Take the square root of all the accumulated squared errors for this // detector group. Assumes Gaussian errors double (*rs)(double) = std::sqrt; std::transform(outputE.begin(), outputE.end(), outputE.begin(), rs); return IvsLam; }
/** * Return a pair of (minimum Q, maximum Q) for given * indirect geometry workspace. Estimates the Q range from all detectors. * If workspace contains grouped detectors/not all detectors are linked * to a spectrum, the returned interval may be larger than actually needed. * @param ws a workspace * @param minE minimum energy transfer in ws * @param maxE maximum energy transfer in ws * @return a pair containing global minimun and maximum Q */ std::pair<double, double> SofQCommon::qBinHintsIndirect(const API::MatrixWorkspace &ws, const double minE, const double maxE) const { using namespace Mantid::PhysicalConstants; auto minQ = std::numeric_limits<double>::max(); auto maxQ = std::numeric_limits<double>::lowest(); const auto &detectorInfo = ws.detectorInfo(); for (size_t i = 0; i < detectorInfo.size(); ++i) { if (detectorInfo.isMasked(i) || detectorInfo.isMonitor(i)) { continue; } const auto twoTheta = detectorInfo.twoTheta(i); const auto &det = detectorInfo.detector(i); const auto Q1 = indirectQ(minE, twoTheta, &det); const auto Q2 = indirectQ(maxE, twoTheta, &det); const auto minmaxQ = std::minmax(Q1, Q2); if (minmaxQ.first < minQ) { minQ = minmaxQ.first; } if (minmaxQ.second > maxQ) { maxQ = minmaxQ.second; } } if (minQ == std::numeric_limits<double>::max()) { throw std::runtime_error("Could not determine Q binning: workspace does " "not contain usable spectra."); } return std::make_pair(minQ, maxQ); }
/** * Return a pair of (minimum Q, maximum Q) for given * direct geometry workspace. * @param ws a workspace * @param minE minimum energy transfer in ws * @param maxE maximum energy transfer in ws * @return a pair containing global minimun and maximum Q */ std::pair<double, double> SofQCommon::qBinHintsDirect(const API::MatrixWorkspace &ws, const double minE, const double maxE) const { using namespace Mantid::PhysicalConstants; auto minTwoTheta = std::numeric_limits<double>::max(); auto maxTwoTheta = std::numeric_limits<double>::lowest(); const auto &spectrumInfo = ws.spectrumInfo(); for (size_t i = 0; i < spectrumInfo.size(); ++i) { if (spectrumInfo.isMasked(i) || spectrumInfo.isMonitor(i)) { continue; } const auto twoTheta = spectrumInfo.twoTheta(i); if (twoTheta < minTwoTheta) { minTwoTheta = twoTheta; } if (twoTheta > maxTwoTheta) { maxTwoTheta = twoTheta; } } if (minTwoTheta == std::numeric_limits<double>::max()) { throw std::runtime_error("Could not determine Q binning: workspace does " "not contain usable spectra."); } std::array<double, 4> q; q[0] = directQ(minE, minTwoTheta); q[1] = directQ(minE, maxTwoTheta); q[2] = directQ(maxE, minTwoTheta); q[3] = directQ(maxE, maxTwoTheta); const auto minmaxQ = std::minmax_element(q.cbegin(), q.cend()); return std::make_pair(*minmaxQ.first, *minmaxQ.second); }
/** * Return the wavelength range of the output histogram. * @param detectorWS [in] :: the input workspace * @param indices [in] :: the workspace indices of foreground histograms * @param refAngles [in] :: the reference angles * @return :: the minimum and maximum virtual wavelengths */ ReflectometrySumInQ::MinMax ReflectometrySumInQ::findWavelengthMinMax( const API::MatrixWorkspace &detectorWS, const Indexing::SpectrumIndexSet &indices, const Angles &refAngles) { const API::SpectrumInfo &spectrumInfo = detectorWS.spectrumInfo(); // Get the new max and min X values of the projected (virtual) lambda range const bool includePartialBins = getProperty(Prop::PARTIAL_BINS); // Find minimum and maximum 2thetas and the corresponding indices. // It cannot be assumed that 2theta increases with indices, check for example // D17 at ILL MinMax inputLambdaRange; MinMax inputTwoThetaRange; for (const auto i : indices) { const auto twoThetas = twoThetaWidth(i, spectrumInfo); inputTwoThetaRange.testAndSetMin(includePartialBins ? twoThetas.min : twoThetas.max); inputTwoThetaRange.testAndSetMax(includePartialBins ? twoThetas.max : twoThetas.min); const auto &edges = detectorWS.binEdges(i); for (size_t xIndex = 0; xIndex < edges.size(); ++xIndex) { // It is common for the wavelength to have negative values at ILL. const auto x = edges[xIndex + (includePartialBins ? 0 : 1)]; if (x > 0.) { inputLambdaRange.testAndSet(x); break; } } if (includePartialBins) { inputLambdaRange.testAndSet(edges.back()); } else { inputLambdaRange.testAndSet(edges[edges.size() - 2]); } } MinMax outputLambdaRange; outputLambdaRange.min = projectToReference(inputLambdaRange.min, inputTwoThetaRange.max, refAngles); outputLambdaRange.max = projectToReference(inputLambdaRange.max, inputTwoThetaRange.min, refAngles); if (outputLambdaRange.min > outputLambdaRange.max) { throw std::runtime_error( "Error projecting lambda range to reference line; projected range (" + std::to_string(outputLambdaRange.min) + "," + std::to_string(outputLambdaRange.max) + ") is negative."); } return outputLambdaRange; }
/** Calculate the derivatives of the newly smoothed data using the spline * Wraps CubicSpline derivative1D * * @param inputWorkspace :: The input workspace * @param outputWorkspace :: The output workspace * @param order :: The order of derivatives to calculate * @param row :: The row of spectra to use */ void SplineSmoothing::calculateDerivatives( const MatrixWorkspace &inputWorkspace, API::MatrixWorkspace &outputWorkspace, const int order, const size_t row) const { const auto &xIn = inputWorkspace.x(row); const double *xValues = &(xIn[0]); double *yValues = &(outputWorkspace.mutableY(order - 1)[0]); const size_t nData = xIn.size(); m_cspline->derivative1D(yValues, xValues, nData, order); }
/** The procedure analyses emode and efixed properties provided to the algorithm *and identify the energy analysis mode and the way the properties are defined *@param workspace :: input workspace which may or may not have incident *energy property (Ei) attached to it as the run log *@param hostAlgorithm :: the pointer to SofQ algorithm hosting the base class. *This algorithm expects to have EMode and EFixed properties attached to it. */ void SofQCommon::initCachedValues(const API::MatrixWorkspace &workspace, API::Algorithm *const hostAlgorithm) { // Retrieve the emode & efixed properties const std::string emode = hostAlgorithm->getProperty("EMode"); // Convert back to an integer representation m_emode = 0; if (emode == "Direct") m_emode = 1; else if (emode == "Indirect") m_emode = 2; m_efixed = hostAlgorithm->getProperty("EFixed"); // Check whether they should have supplied an EFixed value if (m_emode == 1) // Direct { // If GetEi was run then it will have been stored in the workspace, if not // the user will need to enter one if (m_efixed == 0.0) { if (workspace.run().hasProperty("Ei")) { Kernel::Property *p = workspace.run().getProperty("Ei"); Kernel::PropertyWithValue<double> *eiProp = dynamic_cast<Kernel::PropertyWithValue<double> *>(p); if (!eiProp) throw std::runtime_error("Input workspace contains Ei but its " "property type is not a double."); m_efixed = (*eiProp)(); } else { throw std::invalid_argument("Input workspace does not contain an " "EFixed value. Please provide one or run " "GetEi."); } } else { m_efixedGiven = true; } } else { if (m_efixed != 0.0) { m_efixedGiven = true; } } }
/* Convert spectra mask into det-id mask using workspace as source of *spectra-detector maps * * @param sourceWS -- the workspace containing source spectra-detector map * to use on masks * @param maskedSpecID -- vector of spectra id to mask * @param singleDetIds -- output vector of detector ids to mask */ void LoadMask::convertSpMasksToDetIDs(const API::MatrixWorkspace &sourceWS, const std::vector<int32_t> &maskedSpecID, std::vector<int32_t> &singleDetIds) { spec2index_map s2imap = sourceWS.getSpectrumToWorkspaceIndexMap(); detid2index_map sourceDetMap = sourceWS.getDetectorIDToWorkspaceIndexMap(false); std::multimap<size_t, Mantid::detid_t> spectr2index_map; for (auto it = sourceDetMap.begin(); it != sourceDetMap.end(); it++) { spectr2index_map.insert( std::pair<size_t, Mantid::detid_t>(it->second, it->first)); } spec2index_map new_map; for (size_t i = 0; i < maskedSpecID.size(); i++) { // find spectra number from spectra ID for the source workspace const auto itSpec = s2imap.find(maskedSpecID[i]); if (itSpec == s2imap.end()) { throw std::runtime_error( "Can not find spectra with ID: " + boost::lexical_cast<std::string>(maskedSpecID[i]) + " in the workspace" + sourceWS.getName()); } size_t specN = itSpec->second; // find detector range related to this spectra id in the source workspace const auto source_range = spectr2index_map.equal_range(specN); if (source_range.first == spectr2index_map.end()) { throw std::runtime_error("Can not find spectra N: " + boost::lexical_cast<std::string>(specN) + " in the workspace" + sourceWS.getName()); } // add detectors to the masked det-id list for (auto it = source_range.first; it != source_range.second; ++it) { singleDetIds.push_back(it->second); } } }
/** * Construct an "empty" output workspace in virtual-lambda for summation in Q. * * @param detectorWS [in] :: the input workspace * @param indices [in] :: the workspace indices of the foreground histograms * @param refAngles [in] :: the reference angles * @return :: a 1D workspace where y values are all zero */ API::MatrixWorkspace_sptr ReflectometrySumInQ::constructIvsLamWS( const API::MatrixWorkspace &detectorWS, const Indexing::SpectrumIndexSet &indices, const Angles &refAngles) { // Calculate the number of bins based on the min/max wavelength, using // the same bin width as the input workspace const auto &edges = detectorWS.binEdges(refAngles.referenceWSIndex); const double binWidth = (edges.back() - edges.front()) / static_cast<double>(edges.size()); const auto wavelengthRange = findWavelengthMinMax(detectorWS, indices, refAngles); if (std::abs(wavelengthRange.max - wavelengthRange.min) < binWidth) { throw std::runtime_error("Given wavelength range too small."); } const int numBins = static_cast<int>( std::ceil((wavelengthRange.max - wavelengthRange.min) / binWidth)); // Construct the histogram with these X values. Y and E values are zero. const HistogramData::BinEdges bins( numBins + 1, HistogramData::LinearGenerator(wavelengthRange.min, binWidth)); const HistogramData::Counts counts(numBins, 0.); const HistogramData::Histogram modelHistogram(std::move(bins), std::move(counts)); // Create the output workspace API::MatrixWorkspace_sptr outputWS = DataObjects::create<DataObjects::Workspace2D>(detectorWS, 1, std::move(modelHistogram)); // Set the detector IDs and specturm number from the twoThetaR detector. const auto &thetaSpec = detectorWS.getSpectrum(refAngles.referenceWSIndex); auto &outSpec = outputWS->getSpectrum(0); outSpec.clearDetectorIDs(); outSpec.addDetectorIDs(thetaSpec.getDetectorIDs()); outSpec.setSpectrumNo(thetaSpec.getSpectrumNo()); return outputWS; }
/** * Linearly interpolate between the points in integrFlux at xValues and save the * results in yValues. * @param xValues :: X-values at which to interpolate * @param integrFlux :: A workspace with the spectra to interpolate * @param sp :: A workspace index for a spectrum in integrFlux to interpolate. * @param yValues :: A vector to save the results. */ void MDNormSCD::calcIntegralsForIntersections( const std::vector<double> &xValues, const API::MatrixWorkspace &integrFlux, size_t sp, std::vector<double> &yValues) const { assert(xValues.size() == yValues.size()); // the x-data from the workspace const auto &xData = integrFlux.readX(sp); const double xStart = xData.front(); const double xEnd = xData.back(); // the values in integrFlux are expected to be integrals of a non-negative // function // ie they must make a non-decreasing function const auto &yData = integrFlux.readY(sp); size_t spSize = yData.size(); const double yMin = 0.0; const double yMax = yData.back(); size_t nData = xValues.size(); // all integrals below xStart must be 0 if (xValues[nData - 1] < xStart) { std::fill(yValues.begin(), yValues.end(), yMin); return; } // all integrals above xEnd must be equal tp yMax if (xValues[0] > xEnd) { std::fill(yValues.begin(), yValues.end(), yMax); return; } size_t i = 0; // integrals below xStart must be 0 while (i < nData - 1 && xValues[i] < xStart) { yValues[i] = yMin; i++; } size_t j = 0; for (; i < nData; i++) { // integrals above xEnd must be equal tp yMax if (j >= spSize - 1) { yValues[i] = yMax; } else { double xi = xValues[i]; while (j < spSize - 1 && xi > xData[j]) j++; // if x falls onto an interpolation point return the corresponding y if (xi == xData[j]) { yValues[i] = yData[j]; } else if (j == spSize - 1) { // if we get above xEnd it's yMax yValues[i] = yMax; } else if (j > 0) { // interpolate between the consecutive points double x0 = xData[j - 1]; double x1 = xData[j]; double y0 = yData[j - 1]; double y1 = yData[j]; yValues[i] = y0 + (y1 - y0) * (xi - x0) / (x1 - x0); } else // j == 0 { yValues[i] = yMin; } } } }
/** * Finds the median of values in single bin histograms rejecting spectra from * masked * detectors and the results of divide by zero (infinite and NaN). * The median is an average that is less affected by small numbers of very large * values. * @param input :: A histogram workspace with one entry in each bin * @param excludeZeroes :: If true then zeroes will not be included in the * median calculation * @param indexmap :: indexmap * @return The median value of the histograms in the workspace that was passed * to it * @throw out_of_range if a value is negative */ std::vector<double> DetectorDiagnostic::calculateMedian( const API::MatrixWorkspace &input, bool excludeZeroes, const std::vector<std::vector<size_t>> &indexmap) { std::vector<double> medianvec; g_log.debug("Calculating the median count rate of the spectra"); bool checkForMask = false; Geometry::Instrument_const_sptr instrument = input.getInstrument(); if (instrument != nullptr) { checkForMask = ((instrument->getSource() != nullptr) && (instrument->getSample() != nullptr)); } const auto &spectrumInfo = input.spectrumInfo(); for (const auto &hists : indexmap) { std::vector<double> medianInput; const int nhists = static_cast<int>(hists.size()); // The maximum possible length is that of workspace length medianInput.reserve(nhists); PARALLEL_FOR_IF(Kernel::threadSafe(input)) for (int i = 0; i < nhists; ++i) { // NOLINT PARALLEL_START_INTERUPT_REGION if (checkForMask && spectrumInfo.hasDetectors(hists[i])) { if (spectrumInfo.isMasked(hists[i]) || spectrumInfo.isMonitor(hists[i])) continue; } const double yValue = input.readY(hists[i])[0]; if (yValue < 0.0) { throw std::out_of_range("Negative number of counts found, could be " "corrupted raw counts or solid angle data"); } if (!std::isfinite(yValue) || (excludeZeroes && yValue < DBL_EPSILON)) // NaNs/Infs { continue; } // Now we have a good value PARALLEL_CRITICAL(DetectorDiagnostic_median_d) { medianInput.push_back(yValue); } PARALLEL_END_INTERUPT_REGION } PARALLEL_CHECK_INTERUPT_REGION if (medianInput.empty()) { g_log.information( "some group has no valid histograms. Will use 0 for median."); medianInput.push_back(0.); } Kernel::Statistics stats = Kernel::getStatistics(medianInput, StatOptions::Median); double median = stats.median; if (median < 0 || median > DBL_MAX / 10.0) { throw std::out_of_range("The calculated value for the median was either " "negative or unreliably large"); } medianvec.push_back(median); } return medianvec; }
std::vector<std::vector<size_t>> DetectorDiagnostic::makeInstrumentMap(const API::MatrixWorkspace &countsWS) { return { {boost::counting_iterator<std::size_t>(0), boost::counting_iterator<std::size_t>(countsWS.getNumberHistograms())}}; }