Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-26 08:20:24

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #pragma once
0010 
0011 #include "Acts/Utilities/Histogram.hpp"
0012 #include "Acts/Utilities/Logger.hpp"
0013 
0014 #include <array>
0015 #include <functional>
0016 #include <optional>
0017 #include <string>
0018 #include <tuple>
0019 #include <utility>
0020 
0021 namespace ActsExamples {
0022 
0023 /// @brief Outcome of a Gaussian fit to a 1D histogram: `(mean, sigma,
0024 ///        meanError, sigmaError)`
0025 ///
0026 /// A plain @c std::tuple, not a named struct, so a Python fit backend can
0027 /// return an ordinary 4-tuple via `pybind11/stl.h` with no dedicated binding.
0028 using HistogramFitResult = std::tuple<double, double, double, double>;
0029 
0030 /// @brief Fit range `[xMin, xMax]`, closed, selected by bin centre
0031 using HistogramFitRange = std::pair<double, double>;
0032 
0033 /// A single Gaussian fit to a 1D histogram, optionally restricted to a range
0034 ///
0035 /// Any backend, e.g. `ActsPlugins::RootHistogramFit` or a Python callable,
0036 /// can be adapted to this signature.
0037 using HistogramFitFunction = std::function<std::optional<HistogramFitResult>(
0038     const Acts::Experimental::Histogram1&, std::optional<HistogramFitRange>)>;
0039 
0040 /// @brief Mean and width profiles extracted from a histogram of dimension
0041 ///        @c Dim + 1
0042 ///
0043 /// @tparam Dim Number of dimensions of the profiled outer axes
0044 template <std::size_t Dim>
0045 struct MeanWidthProfiles {
0046   /// Fitted mean per bin of the outer axes
0047   Acts::Experimental::Histogram<Dim> mean;
0048   /// Fitted width (sigma) per bin of the outer axes
0049   Acts::Experimental::Histogram<Dim> width;
0050   /// Fraction of bins where a fit was attempted but failed
0051   double fitFailureFraction{};
0052 };
0053 
0054 /// Mean and width profiles extracted from a 2D histogram
0055 using MeanWidthProfiles1 = MeanWidthProfiles<1>;
0056 /// Mean and width profiles extracted from a 3D histogram
0057 using MeanWidthProfiles2 = MeanWidthProfiles<2>;
0058 
0059 /// Fit a Gaussian repeatedly, narrowing the fit range around the peak
0060 ///
0061 /// Each fit after the first is restricted to
0062 /// @f$ m \pm \mathrm{sigmaRange} \cdot s @f$ from the previous iteration.
0063 ///
0064 /// @param fitFn The single-range fit function to iterate
0065 /// @param hist The histogram to fit
0066 /// @param sigmaRange Half-width of the restricted range, in fitted sigmas
0067 /// @param iterations Total number of fits, including the initial unrestricted
0068 ///                   one; values below 1 are treated as 1
0069 /// @param logger Logger for diagnostics on failed iterations
0070 /// @return The fit result, or @c std::nullopt if any iteration failed
0071 std::optional<HistogramFitResult> iterativeFit(
0072     const HistogramFitFunction& fitFn,
0073     const Acts::Experimental::Histogram1& hist, double sigmaRange,
0074     int iterations, const Acts::Logger& logger = Acts::getDummyLogger());
0075 
0076 /// Fit a Gaussian to every slice of a histogram along its last axis
0077 ///
0078 /// For each bin of the outer axes, the distribution along the last axis is
0079 /// fitted with @c iterativeFit and the resulting mean/sigma (with
0080 /// uncertainties) are stored in the corresponding output bin.
0081 ///
0082 /// @param fitFn The single-range fit function to use for every slice
0083 /// @param hist The histogram to profile
0084 /// @param meanName Name for the mean output histogram
0085 /// @param widthName Name for the width output histogram
0086 /// @param minEntriesForFit Slices with fewer entries are skipped
0087 /// @param sigmaRange Half-width of the iterative refit range, in fitted sigmas
0088 /// @param iterations Number of fits per slice, including the unrestricted one
0089 /// @param logger Logger for diagnostics on failed fits
0090 /// @return The mean and width profiles and the fit failure fraction
0091 /// @note Skipped slices leave their output bins empty and do not count towards
0092 ///       @c fitFailureFraction, which reports only genuine fit failures.
0093 template <std::size_t Dim>
0094 MeanWidthProfiles<Dim - 1> extractMeanWidthProfiles(
0095     const HistogramFitFunction& fitFn,
0096     const Acts::Experimental::Histogram<Dim>& hist, const std::string& meanName,
0097     const std::string& widthName, int minEntriesForFit = 5,
0098     double sigmaRange = 3.0, int iterations = 3,
0099     const Acts::Logger& logger = Acts::getDummyLogger()) {
0100   constexpr std::size_t OuterDim = Dim - 1;
0101 
0102   std::array<Acts::Experimental::AxisVariant, OuterDim> axes{};
0103   std::array<int, OuterDim> outerSizes{};
0104   int totalOuterBins = 1;
0105   for (std::size_t d = 0; d < OuterDim; ++d) {
0106     axes[d] = hist.histogram().axis(d);
0107     outerSizes[d] = hist.histogram().axis(d).size();
0108     totalOuterBins *= outerSizes[d];
0109   }
0110 
0111   MeanWidthProfiles<OuterDim> profiles{
0112       Acts::Experimental::Histogram<OuterDim>(meanName, hist.title() + " mean",
0113                                               axes),
0114       Acts::Experimental::Histogram<OuterDim>(widthName,
0115                                               hist.title() + " width", axes),
0116       0.0};
0117 
0118   // Unravel a flat outer index into per-axis indices, last outer axis
0119   // fastest, matching the nested-loop order of the original
0120   // per-dimension overloads
0121   const auto unravel = [&](int flat) {
0122     std::array<int, OuterDim> outerBins{};
0123     int remaining = flat;
0124     for (std::size_t d = OuterDim; d-- > 0;) {
0125       outerBins[d] = remaining % outerSizes[d];
0126       remaining /= outerSizes[d];
0127     }
0128     return outerBins;
0129   };
0130 
0131   int fitFailures = 0;
0132   for (int flat = 0; flat < totalOuterBins; ++flat) {
0133     const std::array<int, OuterDim> outerBins = unravel(flat);
0134     const Acts::Experimental::Histogram1 slice = hist.sliceLastAxis(outerBins);
0135     if (slice.totalContent() < minEntriesForFit) {
0136       // Too few entries: skipped, does not count as a fit failure
0137       continue;
0138     }
0139 
0140     const std::optional<HistogramFitResult> result =
0141         iterativeFit(fitFn, slice, sigmaRange, iterations, logger);
0142     if (!result.has_value()) {
0143       ++fitFailures;
0144       continue;
0145     }
0146 
0147     const auto& [mean, sigma, meanError, sigmaError] = *result;
0148     profiles.mean.setBin(outerBins, mean, meanError);
0149     profiles.width.setBin(outerBins, sigma, sigmaError);
0150   }
0151 
0152   profiles.fitFailureFraction =
0153       (totalOuterBins > 0) ? static_cast<double>(fitFailures) / totalOuterBins
0154                            : 0;
0155 
0156   return profiles;
0157 }
0158 
0159 }  // namespace ActsExamples