/** Create the final output workspace after converting the X axis * @returns the final output workspace * * @param progress :: Progress indicator * @param targetUnit :: Target conversion unit * @param inputWS :: Input workspace * @param nHist :: Stores the number of histograms */ MatrixWorkspace_sptr ConvertSpectrumAxis2::createOutputWorkspace( API::Progress &progress, const std::string &targetUnit, API::MatrixWorkspace_sptr &inputWS, size_t nHist) { // Create the output workspace. Can not re-use the input one because the // spectra are re-ordered. MatrixWorkspace_sptr outputWorkspace = WorkspaceFactory::Instance().create( inputWS, m_indexMap.size(), inputWS->x(0).size(), inputWS->y(0).size()); // Now set up a new numeric axis holding the theta values corresponding to // each spectrum. auto const newAxis = new NumericAxis(m_indexMap.size()); outputWorkspace->replaceAxis(1, newAxis); progress.setNumSteps(nHist + m_indexMap.size()); // Set the units of the axis. if (targetUnit == "theta" || targetUnit == "Theta" || targetUnit == "signed_theta" || targetUnit == "SignedTheta") { newAxis->unit() = boost::make_shared<Units::Degrees>(); } else if (targetUnit == "ElasticQ") { newAxis->unit() = UnitFactory::Instance().create("MomentumTransfer"); } else if (targetUnit == "ElasticQSquared") { newAxis->unit() = UnitFactory::Instance().create("QSquared"); } std::multimap<double, size_t>::const_iterator it; size_t currentIndex = 0; for (it = m_indexMap.begin(); it != m_indexMap.end(); ++it) { // Set the axis value. newAxis->setValue(currentIndex, it->first); // Copy over the data. outputWorkspace->setHistogram(currentIndex, inputWS->histogram(it->second)); // We can keep the spectrum numbers etc. outputWorkspace->getSpectrum(currentIndex) .copyInfoFrom(inputWS->getSpectrum(it->second)); ++currentIndex; progress.report("Creating output workspace..."); } return outputWorkspace; }
/** Generate peaks in the given output workspace * @param functionmap :: map to contain the list of functions with key as their * spectra * @param dataWS :: output matrix workspace */ void GeneratePeaks::generatePeaks( const std::map<specnum_t, std::vector<std::pair<double, API::IFunction_sptr>>> & functionmap, API::MatrixWorkspace_sptr dataWS) { // Calcualte function std::map<specnum_t, std::vector<std::pair<double, API::IFunction_sptr>>>::const_iterator mapiter; for (mapiter = functionmap.begin(); mapiter != functionmap.end(); ++mapiter) { // Get spec id and translated to wsindex in the output workspace specnum_t specid = mapiter->first; specnum_t wsindex; if (m_newWSFromParent) wsindex = specid; else wsindex = m_SpectrumMap[specid]; const std::vector<std::pair<double, API::IFunction_sptr>> &vec_centrefunc = mapiter->second; size_t numpeaksinspec = mapiter->second.size(); for (size_t ipeak = 0; ipeak < numpeaksinspec; ++ipeak) { const std::pair<double, API::IFunction_sptr> ¢refunc = vec_centrefunc[ipeak]; // Determine boundary API::IPeakFunction_sptr thispeak = getPeakFunction(centrefunc.second); double centre = centrefunc.first; double fwhm = thispeak->fwhm(); // const auto &X = dataWS->x(wsindex); double leftbound = centre - m_numPeakWidth * fwhm; if (ipeak > 0) { // Not left most peak. API::IPeakFunction_sptr leftPeak = getPeakFunction(vec_centrefunc[ipeak - 1].second); double middle = 0.5 * (centre + leftPeak->centre()); if (leftbound < middle) leftbound = middle; } auto left = std::lower_bound(X.cbegin(), X.cend(), leftbound); if (left == X.end()) left = X.begin(); double rightbound = centre + m_numPeakWidth * fwhm; if (ipeak != numpeaksinspec - 1) { // Not the rightmost peak IPeakFunction_sptr rightPeak = getPeakFunction(vec_centrefunc[ipeak + 1].second); double middle = 0.5 * (centre + rightPeak->centre()); if (rightbound > middle) rightbound = middle; } auto right = std::lower_bound(left + 1, X.cend(), rightbound); // Build domain & function API::FunctionDomain1DVector domain(left, right); // dataWS->dataX(wsindex)); // Evaluate the function API::FunctionValues values(domain); centrefunc.second->function(domain, values); // Put to output std::size_t offset = (left - X.begin()); std::size_t numY = values.size(); auto &dataY = dataWS->mutableY(wsindex); for (std::size_t i = 0; i < numY; i++) { dataY[i + offset] += values[i]; } } // ENDFOR(ipeak) } }
/**Executes the main part of the algorithm that handles the conversion of the * units * @param inputWS :: the input workspace that will be converted * @throw std::runtime_error :: If the workspace has invalid X axis binning * @return A pointer to a MatrixWorkspace_sptr that contains the converted units */ MatrixWorkspace_sptr ConvertUnits::executeUnitConversion(const API::MatrixWorkspace_sptr inputWS) { // A WS holding BinEdges cannot have less than 2 values, as a bin has // 2 edges, having less than 2 values would mean that the WS contains Points if (inputWS->x(0).size() < 2) { std::stringstream msg; msg << "Input workspace has invalid X axis binning parameters. Should " "have " "at least 2 values. Found " << inputWS->x(0).size() << "."; throw std::runtime_error(msg.str()); } if (inputWS->x(0).front() > inputWS->x(0).back() || inputWS->x(m_numberOfSpectra / 2).front() > inputWS->x(m_numberOfSpectra / 2).back()) throw std::runtime_error("Input workspace has invalid X axis binning " "parameters. X values should be increasing."); MatrixWorkspace_sptr outputWS; // Check whether there is a quick conversion available double factor, power; if (m_inputUnit->quickConversion(*m_outputUnit, factor, power)) // If test fails, could also check whether a quick conversion in the // opposite // direction has been entered { outputWS = this->convertQuickly(inputWS, factor, power); } else { outputWS = this->convertViaTOF(m_inputUnit, inputWS); } // If the units conversion has flipped the ascending direction of X, reverse // all the vectors if (!outputWS->x(0).empty() && (outputWS->x(0).front() > outputWS->x(0).back() || outputWS->x(m_numberOfSpectra / 2).front() > outputWS->x(m_numberOfSpectra / 2).back())) { this->reverse(outputWS); } // Need to lop bins off if converting to energy transfer. // Don't do for EventWorkspaces, where you can easily rebin to recover the // situation without losing information /* This is an ugly test - could be made more general by testing for DBL_MAX values at the ends of all spectra, but that would be less efficient */ if (m_outputUnit->unitID().find("Delta") == 0 && !m_inputEvents) outputWS = this->removeUnphysicalBins(outputWS); // Rebin the data to common bins if requested, and if necessary bool alignBins = getProperty("AlignBins"); if (alignBins && !WorkspaceHelpers::commonBoundaries(outputWS)) outputWS = this->alignBins(outputWS); // If appropriate, put back the bin width division into Y/E. if (m_distribution && !m_inputEvents) // Never do this for event workspaces { this->putBackBinWidth(outputWS); } return outputWS; }
/** Forms the quadrature phase signal (squashogram) * @param ws :: [input] workspace containing the measured spectra * @param phase :: [input] table workspace containing the detector phases * @param n0 :: [input] vector containing the normalization constants * @return :: workspace containing the quadrature phase signal */ API::MatrixWorkspace_sptr PhaseQuadMuon::squash(const API::MatrixWorkspace_sptr &ws, const API::ITableWorkspace_sptr &phase, const std::vector<double> &n0) { // Poisson limit: below this number we consider we don't have enough // statistics // to apply sqrt(N). This is an arbitrary number used in the original code // provided by scientists double poissonLimit = 30.; size_t nspec = ws->getNumberHistograms(); size_t npoints = ws->blocksize(); // Muon life time in microseconds double muLife = PhysicalConstants::MuonLifetime * 1e6; if (n0.size() != nspec) { throw std::invalid_argument("Invalid normalization constants"); } // Get the maximum asymmetry double maxAsym = 0.; for (size_t h = 0; h < nspec; h++) { if (phase->Double(h, 1) > maxAsym) { maxAsym = phase->Double(h, 1); } } if (maxAsym == 0.0) { throw std::invalid_argument("Invalid detector asymmetries"); } std::vector<double> aj, bj; { // Calculate coefficients aj, bj double sxx = 0; double syy = 0; double sxy = 0; for (size_t h = 0; h < nspec; h++) { double asym = phase->Double(h, 1) / maxAsym; double phi = phase->Double(h, 2); double X = n0[h] * asym * cos(phi); double Y = n0[h] * asym * sin(phi); sxx += X * X; syy += Y * Y; sxy += X * Y; } double lam1 = 2 * syy / (sxx * syy - sxy * sxy); double mu1 = 2 * sxy / (sxy * sxy - sxx * syy); double lam2 = 2 * sxy / (sxy * sxy - sxx * syy); double mu2 = 2 * sxx / (sxx * syy - sxy * sxy); for (size_t h = 0; h < nspec; h++) { double asym = phase->Double(h, 1) / maxAsym; double phi = phase->Double(h, 2); double X = n0[h] * asym * cos(phi); double Y = n0[h] * asym * sin(phi); aj.push_back((lam1 * X + mu1 * Y) * 0.5); bj.push_back((lam2 * X + mu2 * Y) * 0.5); } } // First X value double X0 = ws->x(0).front(); // Create and populate output workspace API::MatrixWorkspace_sptr ows = API::WorkspaceFactory::Instance().create( "Workspace2D", 2, npoints + 1, npoints); // X ows->setSharedX(0, ws->sharedX(0)); ows->setSharedX(1, ws->sharedX(0)); // Phase quadrature auto &realY = ows->mutableY(0); auto &imagY = ows->mutableY(1); auto &realE = ows->mutableE(0); auto &imagE = ows->mutableE(1); for (size_t i = 0; i < npoints; i++) { for (size_t h = 0; h < nspec; h++) { // (X,Y,E) with exponential decay removed const double X = ws->x(h)[i]; const double Y = ws->y(h)[i] - n0[h] * exp(-(X - X0) / muLife); const double E = (ws->y(h)[i] > poissonLimit) ? ws->e(h)[i] : sqrt(n0[h] * exp(-(X - X0) / muLife)); realY[i] += aj[h] * Y; imagY[i] += bj[h] * Y; realE[i] += aj[h] * aj[h] * E * E; imagE[i] += bj[h] * bj[h] * E * E; } realE[i] = sqrt(realE[i]); imagE[i] = sqrt(imagE[i]); // Regain exponential decay const double X = ws->getSpectrum(0).x()[i]; const double e = exp(-(X - X0) / muLife); realY[i] /= e; imagY[i] /= e; realE[i] /= e; imagE[i] /= e; } return ows; }
/** Forms the quadrature phase signal (squashogram) * @param ws :: [input] workspace containing the measured spectra * @param phase :: [input] table workspace containing the detector phases * @param n0 :: [input] vector containing the normalization constants * @return :: workspace containing the quadrature phase signal */ API::MatrixWorkspace_sptr PhaseQuadMuon::squash(const API::MatrixWorkspace_sptr &ws, const API::ITableWorkspace_sptr &phase, const std::vector<double> &n0) { // Poisson limit: below this number we consider we don't have enough // statistics // to apply sqrt(N). This is an arbitrary number used in the original code // provided by scientists const double poissonLimit = 30.; // Muon life time in microseconds const double muLife = PhysicalConstants::MuonLifetime * 1e6; const size_t nspec = ws->getNumberHistograms(); if (n0.size() != nspec) { throw std::invalid_argument("Invalid normalization constants"); } auto names = phase->getColumnNames(); for (auto &name : names) { std::transform(name.begin(), name.end(), name.begin(), ::tolower); } auto phaseIndex = findName(phaseNames, names); auto asymmetryIndex = findName(asymmNames, names); // Get the maximum asymmetry double maxAsym = 0.; for (size_t h = 0; h < nspec; h++) { if (phase->Double(h, asymmetryIndex) > maxAsym && phase->Double(h, asymmetryIndex) != ASYMM_ERROR) { maxAsym = phase->Double(h, asymmetryIndex); } } if (maxAsym == 0.0) { throw std::invalid_argument("Invalid detector asymmetries"); } std::vector<bool> emptySpectrum; emptySpectrum.reserve(nspec); std::vector<double> aj, bj; { // Calculate coefficients aj, bj double sxx = 0.; double syy = 0.; double sxy = 0.; for (size_t h = 0; h < nspec; h++) { emptySpectrum.push_back( std::all_of(ws->y(h).begin(), ws->y(h).end(), [](double value) { return value == 0.; })); if (!emptySpectrum[h]) { const double asym = phase->Double(h, asymmetryIndex) / maxAsym; const double phi = phase->Double(h, phaseIndex); const double X = n0[h] * asym * cos(phi); const double Y = n0[h] * asym * sin(phi); sxx += X * X; syy += Y * Y; sxy += X * Y; } } const double lam1 = 2 * syy / (sxx * syy - sxy * sxy); const double mu1 = 2 * sxy / (sxy * sxy - sxx * syy); const double lam2 = 2 * sxy / (sxy * sxy - sxx * syy); const double mu2 = 2 * sxx / (sxx * syy - sxy * sxy); for (size_t h = 0; h < nspec; h++) { if (emptySpectrum[h]) { aj.push_back(0.0); bj.push_back(0.0); } else { const double asym = phase->Double(h, asymmetryIndex) / maxAsym; const double phi = phase->Double(h, phaseIndex); const double X = n0[h] * asym * cos(phi); const double Y = n0[h] * asym * sin(phi); aj.push_back((lam1 * X + mu1 * Y) * 0.5); bj.push_back((lam2 * X + mu2 * Y) * 0.5); } } } const size_t npoints = ws->blocksize(); // Create and populate output workspace API::MatrixWorkspace_sptr ows = API::WorkspaceFactory::Instance().create(ws, 2, npoints + 1, npoints); // X ows->setSharedX(0, ws->sharedX(0)); ows->setSharedX(1, ws->sharedX(0)); // Phase quadrature auto &realY = ows->mutableY(0); auto &imagY = ows->mutableY(1); auto &realE = ows->mutableE(0); auto &imagE = ows->mutableE(1); const auto xPointData = ws->histogram(0).points(); // First X value const double X0 = xPointData.front(); // calculate exponential decay outside of the loop std::vector<double> expDecay = xPointData.rawData(); std::transform(expDecay.begin(), expDecay.end(), expDecay.begin(), [X0, muLife](double x) { return exp(-(x - X0) / muLife); }); for (size_t i = 0; i < npoints; i++) { for (size_t h = 0; h < nspec; h++) { if (!emptySpectrum[h]) { // (X,Y,E) with exponential decay removed const double X = ws->x(h)[i]; const double exponential = n0[h] * exp(-(X - X0) / muLife); const double Y = ws->y(h)[i] - exponential; const double E = (ws->y(h)[i] > poissonLimit) ? ws->e(h)[i] : sqrt(exponential); realY[i] += aj[h] * Y; imagY[i] += bj[h] * Y; realE[i] += aj[h] * aj[h] * E * E; imagE[i] += bj[h] * bj[h] * E * E; } } realE[i] = sqrt(realE[i]); imagE[i] = sqrt(imagE[i]); // Regain exponential decay realY[i] /= expDecay[i]; imagY[i] /= expDecay[i]; realE[i] /= expDecay[i]; imagE[i] /= expDecay[i]; } // New Y axis label ows->setYUnit("Asymmetry"); return ows; }
/** Carries out the bin-by-bin normalization * @param inputWorkspace The input workspace * @param outputWorkspace The result workspace */ void NormaliseToMonitor::normaliseBinByBin( const API::MatrixWorkspace_sptr &inputWorkspace, API::MatrixWorkspace_sptr &outputWorkspace) { EventWorkspace_sptr inputEvent = boost::dynamic_pointer_cast<EventWorkspace>(inputWorkspace); // Only create output workspace if different to input one if (outputWorkspace != inputWorkspace) { if (inputEvent) { outputWorkspace = inputWorkspace->clone(); } else outputWorkspace = WorkspaceFactory::Instance().create(inputWorkspace); } auto outputEvent = boost::dynamic_pointer_cast<EventWorkspace>(outputWorkspace); // Get hold of the monitor spectrum const auto &monX = m_monitor->binEdges(0); auto monY = m_monitor->counts(0); auto monE = m_monitor->countStandardDeviations(0); // Calculate the overall normalization just the once if bins are all matching if (m_commonBins) this->normalisationFactor(monX, monY, monE); const size_t numHists = inputWorkspace->getNumberHistograms(); auto specLength = inputWorkspace->blocksize(); // Flag set when a division by 0 is found bool hasZeroDivision = false; Progress prog(this, 0.0, 1.0, numHists); // Loop over spectra PARALLEL_FOR_IF( Kernel::threadSafe(*inputWorkspace, *outputWorkspace, *m_monitor)) for (int64_t i = 0; i < int64_t(numHists); ++i) { PARALLEL_START_INTERUPT_REGION prog.report(); const auto &X = inputWorkspace->binEdges(i); // If not rebinning, just point to our monitor spectra, otherwise create new // vectors auto Y = (m_commonBins ? monY : Counts(specLength)); auto E = (m_commonBins ? monE : CountStandardDeviations(specLength)); if (!m_commonBins) { // ConvertUnits can give X vectors of all zeros - skip these, they cause // problems if (X.back() == 0.0 && X.front() == 0.0) continue; // Rebin the monitor spectrum to match the binning of the current data // spectrum VectorHelper::rebinHistogram( monX.rawData(), monY.mutableRawData(), monE.mutableRawData(), X.rawData(), Y.mutableRawData(), E.mutableRawData(), false); // Recalculate the overall normalization factor this->normalisationFactor(X, Y, E); } if (inputEvent) { // ----------------------------------- EventWorkspace // --------------------------------------- EventList &outEL = outputEvent->getSpectrum(i); outEL.divide(X.rawData(), Y.mutableRawData(), E.mutableRawData()); } else { // ----------------------------------- Workspace2D // --------------------------------------- auto &YOut = outputWorkspace->mutableY(i); auto &EOut = outputWorkspace->mutableE(i); const auto &inY = inputWorkspace->y(i); const auto &inE = inputWorkspace->e(i); outputWorkspace->mutableX(i) = inputWorkspace->x(i); // The code below comes more or less straight out of Divide.cpp for (size_t k = 0; k < specLength; ++k) { // Get the input Y's const double leftY = inY[k]; const double rightY = Y[k]; if (rightY == 0.0) { hasZeroDivision = true; } // Calculate result and store in local variable to avoid overwriting // original data if // output workspace is same as one of the input ones const double newY = leftY / rightY; if (fabs(rightY) > 1.0e-12 && fabs(newY) > 1.0e-12) { const double lhsFactor = (inE[k] < 1.0e-12 || fabs(leftY) < 1.0e-12) ? 0.0 : pow((inE[k] / leftY), 2); const double rhsFactor = E[k] < 1.0e-12 ? 0.0 : pow((E[k] / rightY), 2); EOut[k] = std::abs(newY) * sqrt(lhsFactor + rhsFactor); } // Now store the result YOut[k] = newY; } // end Workspace2D case } // end loop over current spectrum PARALLEL_END_INTERUPT_REGION } // end loop over spectra PARALLEL_CHECK_INTERUPT_REGION if (hasZeroDivision) { g_log.warning() << "Division by zero in some of the bins.\n"; } }