improve the doxygen docu of PRunSingleHistoRRF.*

This commit is contained in:
2025-11-23 17:25:45 +01:00
parent db4b6395ef
commit 633f63b104
2 changed files with 1116 additions and 118 deletions

View File

@@ -33,59 +33,502 @@
#include "PRunBase.h"
/**
* <p>Class handling single histogram fit type.
* \brief Class for fitting single histogram data in a Rotating Reference Frame (RRF).
*
* PRunSingleHistoRRF implements single histogram analysis with Rotating Reference Frame
* transformation. This technique is essential for high transverse field (TF) μSR
* measurements where the precession frequency is too high to resolve directly.
*
* \section rrf_physics Physics Background
*
* <b>The Problem with High-Field μSR:</b>
* - At fields B > ~0.5 T, muon precession frequency ω = γ_μ B exceeds ~70 MHz
* - Standard time-domain analysis struggles: requires sub-ns binning → low statistics
* - Frequency-domain (Fourier) analysis has limited resolution
*
* <b>The RRF Solution:</b>
* - Transform to a reference frame rotating at frequency ω_RRF ≈ ω_signal
* - In rotating frame: effective frequency Δω = ω_signal - ω_RRF << ω_signal
* - Low-frequency signal can be fit with standard time-domain techniques
* - Preserves full information: relaxation rates, frequency distributions
*
* \section rrf_transform Mathematical Transformation
*
* <b>Data processing steps:</b>
* -# <b>Background subtraction:</b> N(t) → N(t) - B
* -# <b>Lifetime correction:</b> N(t) - B → [N(t) - B] × exp(+t/τ_μ) = M(t)
* -# <b>N₀ estimation:</b> Fit M(t) over initial time window to extract N₀
* -# <b>Asymmetry extraction:</b> A(t) = M(t)/N₀ - 1
* -# <b>RRF rotation:</b> A_RRF(t) = 2 × A(t) × cos(ω_RRF t + φ_RRF)
* -# <b>RRF packing:</b> Average over multiple bins to filter high-frequency components
*
* The factor of 2 in step 5 compensates for the loss of the counter-rotating component
* when the high-frequency part is suppressed by packing.
*
* <b>Theory function in RRF:</b>
* The polarization function P(t) transforms as:
* \f[
* P_{\rm RRF}(t) = 2 \cdot P(t) \cdot \cos(\omega_{\rm RRF} t + \phi_{\rm RRF})
* \f]
*
* For a simple oscillation P(t) = A·cos(ωt + φ)·exp(-λt), this becomes:
* \f[
* P_{\rm RRF}(t) = A \cdot [\cos((\omega - \omega_{\rm RRF})t + \phi - \phi_{\rm RRF}) + \text{high freq.}] \cdot e^{-\lambda t}
* \f]
*
* After RRF packing, only the low-frequency component Δω = ω - ω_RRF remains.
*
* \section rrf_requirements GLOBAL Block Requirements
*
* RRF analysis REQUIRES the following entries in the GLOBAL block:
* - <b>rrf_freq:</b> RRF rotation frequency (value + unit: MHz, Mc, T, G, kG)
* - <b>rrf_phase:</b> RRF initial phase in degrees
* - <b>rrf_packing:</b> Number of bins to average (filters high frequencies)
*
* \section rrf_msr MSR File Example
*
* \code
* GLOBAL
* rrf_freq 13.554 T # RRF frequency equivalent to 13.554 Tesla
* rrf_phase 45.0 # RRF phase in degrees
* rrf_packing 25 # Average 25 bins to suppress high-freq component
* data 200 60000 # Data range (fgb lgb)
* fit 0.1 8.0 # Fit range in μs
*
* RUN data/run2425 PSI MUE4 PSI MUSR-ROOT
* fittype 8 (SingleHistoRRF)
* map 1
* forward 1
* background 50 150
* t0 210.5
* \endcode
*
* \section rrf_workflow Data Processing Workflow
*
* 1. <b>PrepareData():</b> Load raw histogram, determine t0, group/add histograms
* 2. <b>PrepareFitData():</b>
* - Subtract background (estimated or fixed)
* - Apply lifetime correction: exp(+t/τ_μ)
* - Estimate N₀ using Fourier analysis to find main frequency
* - Extract asymmetry: A(t) = M(t)/N₀ - 1
* - Apply RRF rotation: A_RRF(t) = 2·A(t)·cos(ω_RRF t + φ_RRF)
* - Pack data (average over rrf_packing bins)
* - Calculate errors with proper propagation
*
* \section rrf_errors Error Propagation
*
* RRF asymmetry error (unpacked):
* \f[
* \sigma_{A}(t) = \frac{e^{t/\tau_\mu}}{N_0} \sqrt{N(t) + \left(\frac{N(t)-B}{N_0}\right)^2 \sigma_{N_0}^2}
* \f]
*
* After RRF packing (n bins):
* \f[
* \sigma_{A_{\rm RRF}}^{\rm packed} = \frac{\sqrt{2}}{n} \sqrt{\sum_{i=1}^{n} \sigma_{A}^2(t_i)}
* \f]
*
* The √2 factor accounts for the doubling in the RRF transformation.
*
* \section rrf_applications Applications
*
* <b>High-TF μSR experiments:</b>
* - Knight shift measurements (NMR-like)
* - Vortex state studies in type-II superconductors
* - Diamagnetic/paramagnetic shift measurements
* - Internal field distributions in magnets
*
* \see PRunSingleHisto for standard (non-RRF) single histogram analysis
* \see PRunAsymmetryRRF for RRF asymmetry (forward-backward) analysis
* \see PRunBase for base class interface
*/
class PRunSingleHistoRRF : public PRunBase
{
public:
/**
* \brief Default constructor creating an empty, invalid RRF single histogram run object.
*
* Initializes member variables to default/invalid states:
* - fNoOfFitBins = 0
* - fBackground = 0
* - fBkgErr = 1.0 (default error estimate)
* - fRRFPacking = -1 (invalid, must be set from GLOBAL block)
* - fTheoAsData = false
* - fGoodBins[0,1] = -1 (unset)
* - fN0EstimateEndTime = 1.0 μs
*
* This constructor exists for container compatibility. The resulting object
* cannot be used until properly initialized.
*/
PRunSingleHistoRRF();
/**
* \brief Main constructor initializing RRF single histogram run from MSR file and data.
*
* Performs comprehensive validation and initialization:
*
* 1. <b>GLOBAL Block Validation (MANDATORY):</b>
* - Checks GLOBAL block is present (required for RRF)
* - Validates RRF frequency is specified (rrf_freq)
* - Validates RRF packing is specified (rrf_packing)
* - Sets fValid=false and returns on any validation failure
*
* 2. <b>Member Initialization:</b>
* - Extracts fRRFPacking from GLOBAL block
* - Sets fN0EstimateEndTime = 1.0 μs
* - Initializes fGoodBins to -1 (determined in PrepareData)
*
* 3. <b>Data Preparation:</b>
* - Calls PrepareData() to load histogram and perform RRF transformation
* - Sets fValid=false if data preparation fails
*
* \param msrInfo Pointer to MSR file handler (must remain valid)
* \param rawData Pointer to raw data handler for histogram loading
* \param runNo Run number (0-based index in MSR file RUN blocks)
* \param tag Operation mode: kFit (fitting), kView (display/plotting)
* \param theoAsData Theory resolution: true = at data points, false = 8× finer grid
*
* \warning Always check IsValid() after construction. GLOBAL block with RRF
* parameters is MANDATORY for this fit type.
*
* \see PrepareData() for RRF transformation details
*/
PRunSingleHistoRRF(PMsrHandler *msrInfo, PRunDataHandler *rawData, UInt_t runNo, EPMusrHandleTag tag, Bool_t theoAsData);
/**
* \brief Virtual destructor releasing allocated resources.
*
* Clears the forward histogram vector. Base class handles theory objects.
*/
virtual ~PRunSingleHistoRRF();
/**
* \brief Calculates χ² between RRF-transformed data and theory.
*
* Computes chi-squared for RRF single histogram fitting:
* \f[
* \chi^2 = \sum_{i=t_{\rm start}}^{t_{\rm end}} \frac{[A_{\rm RRF}^{\rm data}(t_i) - A_{\rm RRF}^{\rm theory}(t_i)]^2}{\sigma_i^2}
* \f]
*
* <b>Algorithm:</b>
* -# Evaluate user-defined FUNCTIONS with current parameters
* -# Pre-evaluate theory at t=1.0 to initialize LF/user functions (thread-safety)
* -# Loop over fit range with OpenMP parallelization
* -# Sum squared differences weighted by inverse variance
*
* <b>OpenMP Parallelization:</b>
* - Dynamic scheduling with chunk = max(10, N_bins / N_processors)
* - Reduction on chisq accumulator
*
* \param par Parameter vector from MINUIT minimizer
* \return χ² value (sum over all bins in fit range)
*
* \note Unlike standard single histogram, no N₀/exp(-t/τ) factors needed since
* RRF transformation already produces dimensionless asymmetry.
*/
virtual Double_t CalcChiSquare(const std::vector<Double_t>& par);
/**
* \brief Calculates expected χ² using theory variance instead of data variance.
*
* Computes expected chi-squared where the error estimate comes from the
* theory prediction rather than the data:
* \f[
* \chi^2_{\rm exp} = \sum_{i} \frac{[A_{\rm RRF}^{\rm data}(t_i) - A_{\rm RRF}^{\rm theory}(t_i)]^2}{A_{\rm RRF}^{\rm theory}(t_i)}
* \f]
*
* This is useful for diagnostic purposes to check fit quality.
*
* \param par Parameter vector from MINUIT
* \return Expected χ² value
*/
virtual Double_t CalcChiSquareExpected(const std::vector<Double_t>& par);
/**
* \brief Calculates maximum likelihood (not yet implemented for RRF).
*
* Maximum likelihood estimation for RRF data is more complex due to the
* transformation from Poisson-distributed counts to RRF asymmetry.
* Currently returns 0.0.
*
* \param par Parameter vector from MINUIT
* \return 0.0 (not implemented)
*
* \todo Implement proper ML for RRF data accounting for error propagation
* through the RRF transformation.
*/
virtual Double_t CalcMaxLikelihood(const std::vector<Double_t>& par);
/**
* \brief Evaluates theory function at all data points for viewing/plotting.
*
* Calculates RRF theory values using current fit parameters stored in
* the MSR parameter list. The theory function P(t) is evaluated at each
* data time point and results are stored in fData for display.
*
* \note Theory is evaluated directly in the RRF frame, not transformed.
* The THEORY block should specify the low-frequency RRF signal.
*/
virtual void CalcTheory();
/**
* \brief Returns the number of bins included in the current fit range.
*
* Triggers CalcNoOfFitBins() to ensure bin count is current, then returns
* the cached value.
*
* \return Number of RRF-packed bins within [fFitStartTime, fFitEndTime]
*/
virtual UInt_t GetNoOfFitBins();
/**
* \brief Sets fit range using bin-offset syntax from COMMANDS block.
*
* Parses fit range specification in the format:
* - "FIT_RANGE fgb+n0 lgb-n1" (single range for all runs)
* - "FIT_RANGE fgb+n00 lgb-n01 fgb+n10 lgb-n11 ..." (per-run ranges)
*
* where fgb = first good bin, lgb = last good bin, and nXY are offsets.
*
* Converts bin specification to time using:
* - fFitStartTime = (fgb + offset - t0) × time_resolution
* - fFitEndTime = (lgb - offset - t0) × time_resolution
*
* \param fitRange String containing FIT_RANGE specification
*
* \note This is called when COMMANDS block modifies fit range during fitting.
*/
virtual void SetFitRangeBin(const TString fitRange);
/**
* \brief Returns the first bin index in the fit range.
* \return Start bin index (0-based, after RRF packing)
*/
virtual Int_t GetStartTimeBin() { return fStartTimeBin; }
/**
* \brief Returns the last bin index in the fit range (exclusive).
* \return End bin index (loop condition: i < fEndTimeBin)
*/
virtual Int_t GetEndTimeBin() { return fEndTimeBin; }
/**
* \brief Calculates start/end bin indices from fit time range.
*
* Converts fit range times (μs) to RRF-packed bin indices:
* - fStartTimeBin = ceil((fFitStartTime - dataTimeStart) / dataTimeStep)
* - fEndTimeBin = floor((fFitEndTime - dataTimeStart) / dataTimeStep) + 1
*
* Also updates fNoOfFitBins = fEndTimeBin - fStartTimeBin.
*
* \note Time step accounts for RRF packing: dataTimeStep = rawTimeStep × fRRFPacking
*/
virtual void CalcNoOfFitBins();
protected:
/**
* \brief Main data preparation orchestrator for RRF single histogram analysis.
*
* Coordinates loading and preprocessing of histogram data:
* -# Validates GLOBAL block presence and RRF parameters
* -# Retrieves raw run data from data handler
* -# Extracts metadata (field, energy, temperature)
* -# Collects histogram numbers from RUN block forward specification
* -# Validates histograms exist in data file
* -# Determines time resolution (ns → μs conversion)
* -# Determines t0 values via GetProperT0()
* -# Handles addrun (co-adding multiple runs)
* -# Handles grouping (combining multiple detectors)
* -# Determines data range via GetProperDataRange()
* -# Determines fit range via GetProperFitRange()
* -# Calls PrepareFitData() or PrepareViewData() based on tag
*
* \return true if data preparation succeeds, false on any error
*
* \see PrepareFitData(), PrepareViewData()
*/
virtual Bool_t PrepareData();
/**
* \brief Performs full RRF transformation for fitting.
*
* Transforms raw histogram to RRF asymmetry through these steps:
*
* 1. <b>Frequency Analysis:</b>
* - Calls GetMainFrequency() to find dominant precession frequency
* - Used to determine optimal N₀ estimation window
*
* 2. <b>Background Handling:</b>
* - If background range given: estimate from data via EstimateBkg()
* - If fixed background given: use directly
* - Subtract background: N(t) → N(t) - B
*
* 3. <b>Lifetime Correction:</b>
* - Apply exp(+t/τ_μ) to remove exponential decay
* - Store as M(t) = [N(t) - B] × exp(+t/τ_μ)
* - Calculate M_err = exp(+t/τ_μ) × √(N(t) + σ_B²)
*
* 4. <b>N₀ Estimation:</b>
* - Call EstimateN0() to determine normalization
* - Uses weighted average over full oscillation cycles
*
* 5. <b>Asymmetry Extraction:</b>
* - A(t) = M(t)/N₀ - 1
* - A_err(t) = exp(+t/τ)/N₀ × √(N(t) + [(N(t)-B)/N₀]² × σ_N₀²)
*
* 6. <b>RRF Rotation:</b>
* - A_RRF(t) = 2 × A(t) × cos(ω_RRF × t + φ_RRF)
* - Factor 2 compensates for discarded counter-rotating component
*
* 7. <b>RRF Packing:</b>
* - Average over fRRFPacking consecutive bins
* - Error: σ_packed = √(2 × Σσ²) / n
*
* 8. <b>Time Grid Setup:</b>
* - Set data time start accounting for packing offset
* - Set data time step = raw_resolution × fRRFPacking
*
* \param runData Raw run data handler
* \param histoNo Forward histogram number (0-based)
* \return true on success, false on error
*/
virtual Bool_t PrepareFitData(PRawRunData* runData, const UInt_t histoNo);
/**
* \brief Prepares RRF data for viewing/plotting.
*
* Similar to PrepareFitData() but additionally:
* - Handles view packing (if specified and > RRF packing)
* - Sets up theory calculation grid (8× finer than data if fTheoAsData=false)
* - Evaluates theory function for plotting overlay
*
* \param runData Raw run data handler
* \param histoNo Forward histogram number
* \return true on success, false on error
*
* \note View packing < RRF packing is ignored with warning since RRF
* packing is already applied during data transformation.
*/
virtual Bool_t PrepareViewData(PRawRunData* runData, const UInt_t histoNo);
private:
Double_t fN0EstimateEndTime; ///< end time in (us) over which N0 is estimated.
Double_t fN0EstimateEndTime; ///< End time (μs) for N₀ estimation window. Rounded to integer number of oscillation cycles based on main frequency.
UInt_t fNoOfFitBins; ///< number of bins to be fitted
Double_t fBackground; ///< needed if background range is given (units: 1/bin)
Double_t fBkgErr; ///< estimate error on the estimated background
Int_t fRRFPacking; ///< RRF packing for this particular run. Given in the GLOBAL-block.
Bool_t fTheoAsData; ///< true=only calculate the theory points at the data points, false=calculate more points for the theory as compared to data are calculated which lead to 'nicer' Fouriers
UInt_t fNoOfFitBins; ///< Number of RRF-packed bins within fit range [fStartTimeBin, fEndTimeBin)
Double_t fBackground; ///< Estimated or fixed background level in counts/bin (before packing)
Double_t fBkgErr; ///< Statistical error on background estimate (std dev of background region)
Int_t fRRFPacking; ///< RRF packing factor from GLOBAL block (number of raw bins averaged together)
Bool_t fTheoAsData; ///< Theory resolution mode: true = at data points only, false = 8× finer grid for smooth Fourier transforms
Int_t fGoodBins[2]; ///< keep first/last good bins. 0=fgb, 1=lgb
Int_t fGoodBins[2]; ///< Good bin range: [0] = first good bin (fgb), [1] = last good bin (lgb). Used for COMMANDS block fit range specification.
Int_t fStartTimeBin; ///< bin at which the fit starts
Int_t fEndTimeBin; ///< bin at which the fit ends
Int_t fStartTimeBin; ///< First bin index in fit range (inclusive, 0-based in RRF-packed data)
Int_t fEndTimeBin; ///< Last bin index in fit range (exclusive: loop as i < fEndTimeBin)
PDoubleVector fForward; ///< forward histo data
PDoubleVector fM; ///< vector holding M(t) = [N(t)-N_bkg] exp(+t/tau). Needed to estimate N0.
PDoubleVector fMerr; ///< vector holding the error of M(t): M_err = exp(+t/tau) sqrt(N(t)).
PDoubleVector fW; ///< vector holding the weight needed to estimate N0, and errN0.
PDoubleVector fAerr; ///< vector holding the errors of estimated A(t)
PDoubleVector fForward; ///< Forward detector histogram data (progressively transformed during preparation)
PDoubleVector fM; ///< Lifetime-corrected histogram: M(t) = [N(t) - B] × exp(+t/τ_μ). Used for N₀ estimation.
PDoubleVector fMerr; ///< Error on M(t): σ_M = exp(+t/τ_μ) × √(N(t) + σ_B²). Includes background error.
PDoubleVector fW; ///< Weights for N₀ estimation: W(t) = 1/σ_M². Used in weighted average.
PDoubleVector fAerr; ///< Asymmetry errors before RRF packing. Used for packed error calculation.
/**
* \brief Determines and validates t0 values for all histograms.
*
* Searches for t0 in order of priority:
* -# RUN block t0 specification
* -# GLOBAL block t0 specification
* -# Data file header t0 values
* -# Automatic estimation (with warning)
*
* Also handles addt0 for addrun histograms.
*
* \param runData Raw run data for histogram access
* \param globalBlock GLOBAL block with default t0 values
* \param histoNo Vector of histogram indices to process
* \return true if valid t0 found for all histograms, false on error
*/
virtual Bool_t GetProperT0(PRawRunData* runData, PMsrGlobalBlock *globalBlock, PUIntVector &histoNo);
/**
* \brief Determines valid data range (first/last good bins).
*
* Establishes analysis boundaries in order of priority:
* -# RUN block data range specification
* -# GLOBAL block data range specification
* -# Automatic estimation (t0 + 10ns offset to end of histogram)
*
* Validates that range is sensible (start < end, within histogram bounds).
*
* \return true if valid data range determined, false on error
*/
virtual Bool_t GetProperDataRange();
/**
* \brief Determines fit time range from MSR file settings.
*
* Extracts fit range in order of priority:
* -# RUN block fit range (time-based or bin-based)
* -# GLOBAL block fit range (time-based or bin-based)
* -# Default to data range (fgb to lgb) with warning
*
* Converts bin-based specifications to time using t0 and time resolution.
*
* \param globalBlock GLOBAL block with default fit settings
*/
virtual void GetProperFitRange(PMsrGlobalBlock *globalBlock);
/**
* \brief Finds the dominant precession frequency in raw data.
*
* Performs Fourier transform on raw histogram data to identify the
* main muon precession frequency:
* -# Creates TH1F from data in good bin range
* -# Applies strong apodization (windowing)
* -# Computes power spectrum
* -# Searches for maximum above 10 MHz (ignores DC component)
*
* The frequency is used to:
* - Determine optimal N₀ estimation window (integer cycles)
* - Calculate suggested "optimal packing" for user information
*
* \param data Raw histogram data vector
* \return Maximum frequency in MHz, or 0 if not found
*/
virtual Double_t GetMainFrequency(PDoubleVector &data);
/**
* \brief Estimates initial normalization N₀ from lifetime-corrected data.
*
* Calculates N₀ as weighted average of M(t) over initial time window:
* \f[
* N_0 = \frac{\sum_{i=0}^{n} M(t_i)}{n}
* \f]
*
* The window length is chosen to include an integer number of complete
* oscillation cycles (based on freqMax) within fN0EstimateEndTime.
*
* Error estimate:
* \f[
* \sigma_{N_0} = \frac{\sqrt{\sum w_i^2 \sigma_{M_i}^2}}{\sum w_i}
* \f]
*
* \param errN0 [out] Estimated error on N₀
* \param freqMax Main precession frequency (MHz) for window calculation
* \return Estimated N₀ value
*/
virtual Double_t EstimateN0(Double_t &errN0, Double_t freqMax);
/**
* \brief Estimates background from pre-t0 bins.
*
* Calculates background level and error from specified background range:
* -# Validates background range is within histogram bounds
* -# Adjusts range to integer number of accelerator RF cycles (PSI/RAL/TRIUMF)
* -# Computes mean and standard deviation
* -# Stores in fBackground and fBkgErr
*
* \param histoNo Histogram index (for error messages)
* \return true on success, false if background range invalid
*/
virtual Bool_t EstimateBkg(UInt_t histoNo);
};