Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-09-17 09:13:39

0001 #ifndef RIVET_Cutflow_HH
0002 #define RIVET_Cutflow_HH
0003 
0004 #include "Rivet/Tools/RivetYODA.hh"
0005 #include "Rivet/Tools/Utils.hh"
0006 
0007 #include <iomanip>
0008 #include <iostream>
0009 #include <vector>
0010 #include <sstream>
0011 #include <string>
0012 
0013 namespace Rivet {
0014 
0015   namespace {
0016     template<typename T>
0017     std::vector<T> operator+(std::vector<T>&& res, const std::vector<T>& vadd) {
0018       res.insert(std::end(res), std::begin(vadd), std::end(vadd));
0019       return std::move(res);
0020     }
0021   }
0022 
0023   /// A tracker of numbers & fractions of events passing sequential cuts
0024   class Cutflow : public YODA::BinnedHisto<std::string> {
0025   public:
0026 
0027     using BaseT = YODA::BinnedHisto<std::string>;
0028     using BinningT = BaseT::BinningT;
0029     using FillType = BaseT::FillType;
0030     using BinType = BaseT::BinT;
0031     using Ptr = std::shared_ptr<Cutflow>;
0032     using AnalysisObject::operator =;
0033 
0034     /// @brief Nullary constructor
0035     Cutflow() : BaseT(), icurr(0) {}
0036 
0037     /// @brief Constructor using binning object
0038     Cutflow(const BinningT& binning) : BaseT(binning), icurr(0) { }
0039 
0040     /// @brief Constructor using rvalue binning object
0041     Cutflow(BinningT&& binning) : BaseT(std::move(binning)), icurr(0) { }
0042 
0043     /// Proper constructor using a cutflow name and a list of edges
0044     ///
0045     /// @note the constructor will preprend the empty string as a pre-cut step.
0046     Cutflow(const std::vector<std::string>& edges, const std::string& path = "")
0047       : BaseT(std::vector<std::string>{""} + edges, path), icurr(0) { }
0048 
0049     /// @brief Fill function with FillType
0050     ///
0051     /// @note This method will set the current fill position icurr.
0052     virtual int fill(FillType&& coords, const double weight = 1.0, const double fraction = 1.0) {
0053       int pos = BaseT::fill(std::move(coords), weight, fraction);
0054       icurr = (size_t)pos; // cannot be negative for discrete AOs
0055       return pos;
0056     }
0057 
0058     /// @brief Fill function using an explicit coordinate
0059     //
0060     /// @note This method will set the current fill position icurr.
0061     virtual int fill(std::string val, const double weight = 1.0, const double fraction = 1.0) {
0062       int pos = BaseT::fill({val}, weight, fraction);
0063       icurr = (size_t)pos; // cannot be negative for discrete AOs
0064       return pos;
0065     }
0066 
0067     /// @brief Convenience method to fill the pre-cut step
0068     virtual int fillinit(const double weight=1.0, const double fraction=1.0) {
0069       return fill(""s, weight, fraction);
0070     }
0071 
0072     /// @brief Convenience method to fill the next cut step if @a cutresult is true
0073     virtual int fillnext(const bool cutresult, const double weight = 1.0, const double fraction = 1.0) {
0074       if (!cutresult)  return 0;
0075       const std::string edge = BaseT::bin(++icurr).xEdge();
0076       return fill(edge, weight, fraction);
0077     }
0078 
0079     /// @brief Convenience method to fill the next cut step
0080     virtual int fillnext(const double weight = 1.0, const double fraction = 1.0) {
0081       return fillnext(true, weight, fraction);
0082     }
0083 
0084     /// @brief Convenience method to fill the next cut steps from an n-element results vector @a cutresult
0085     virtual int fillnext(const std::vector<bool>& cutresults, const double weight = 1.0, const double fraction = 1.0) {
0086       if (icurr+cutresults.size() > BaseT::numBins()+1) {
0087         throw RangeError("Number of filled cut results needs to match the Cutflow construction (in cutflow '"+BaseT::path()+"')");
0088       }
0089       for (size_t i = 0; i < cutresults.size(); ++i) {
0090         if (!cutresults[i])  return 0;
0091         fillnext(weight, fraction); // also incremenrs icurr
0092       }
0093       return (int)icurr;
0094     }
0095 
0096     /// Returns current cutflow index for debugging
0097     size_t currentIndex() const {  return icurr; }
0098 
0099     /// Scale the cutflow weights by the given factor
0100     void scale(const double factor) {
0101       BaseT::scaleW(factor);
0102     }
0103 
0104     /// Scale the cutflow weights so that the weight count after cut @a edge is @a norm
0105     void normalizeStep(const std::string& edge, const double norm) {
0106       const auto& b = BaseT::binAt(edge);
0107       if (b.sumW() == 0) {
0108         MSG_WARNING("Failed to scale Cutflow " << BaseT::path() << " as bin " << edge << " is empty");
0109         return;
0110       }
0111       scale(norm/b.sumW());
0112     }
0113 
0114     /// Alias to scale the cutflow weights so that the weight after the pre-init cut is @a norm
0115     void normalizeFirst(const double norm) {
0116       normalizeStep(""s, norm);
0117     }
0118 
0119     /// Create a string representation
0120     string str() const {
0121       using namespace std;
0122       stringstream ss;
0123       ss << fixed << std::setprecision(1) << BaseT::bin(1).sumW();
0124       const size_t weight0len = ss.str().length();
0125       ss << fixed << std::setprecision(1) << BaseT::bin(1).effNumEntries();
0126       const size_t count0len = ss.str().length();
0127       ss.str("");
0128       ss << BaseT::path() << " cut-flow:\n";
0129       size_t maxnamelen = 0;
0130       for (const string& edge : BaseT::xEdges()) {
0131         maxnamelen = std::max(edge.length(), maxnamelen);
0132       }
0133       ss << setw(maxnamelen+5) << "" << "   "
0134          << setw(weight0len) << right << "Weight" << "   "
0135          << setw(count0len) << right << "Count" << "    "
0136          << setw(6) << right << "A_cumu" << "    "
0137          << setw(6) << right << "A_incr";
0138       //for (size_t i = 0; i <= ncuts; ++i) {
0139       const double wtot = BaseT::bin(1).sumW();
0140       double wlast = wtot;
0141       for (const auto& bin : BaseT::bins()) {
0142         const size_t i = bin.index();
0143         const int pcttot = (wtot == 0) ? -1 : round(100*bin.sumW()/wtot);
0144         const int pctinc = (i == 1 || wlast == 0) ? -1 : round(100*bin.sumW()/wlast);
0145         wlast = bin.sumW();
0146         stringstream ss2;
0147         ss2 << fixed << setprecision(1) << bin.sumW();
0148         const string weightstr = ss2.str(); ss2.str("");
0149         ss2 << fixed << setprecision(1) << bin.effNumEntries();
0150         const string countstr = ss2.str(); ss2.str("");
0151         ss2 << fixed << setprecision(3) << pcttot << "%";
0152         const string pcttotstr = ss2.str(); ss2.str("");
0153         ss2 << fixed << setprecision(3) << pctinc << "%";
0154         const string pctincstr = ss2.str();
0155         ss << "\n"
0156            << setw(maxnamelen+5) << left << (i == 1 ? "" : "Pass "+BaseT::bin(i).xEdge()) << "   "
0157            << setw(weight0len) << right << weightstr << "    "
0158            << setw(count0len) << right << countstr << "    "
0159            << setw(6) << right << (pcttot < 0 ? "- " : pcttotstr) << "    "
0160            << setw(6) << right << (pctinc < 0 ? "- " : pctincstr);
0161       }
0162       return ss.str();
0163     }
0164 
0165     /// Print string representation to a stream
0166     void print(std::ostream& os) const {
0167       os << str() << std::flush;
0168     }
0169 
0170   protected:
0171 
0172     /// Get a logger object.
0173     Log& getLog() const {
0174       return Rivet::Log::getLog("Rivet.Cutflow");
0175     }
0176 
0177     size_t icurr;
0178 
0179   };
0180 
0181 
0182   /// FillCollector specialisation for all Cutflow-like AO
0183   template <>
0184   class FillCollector<Cutflow> : public Cutflow {
0185   public:
0186 
0187     using YAO = Cutflow;
0188     using Ptr = shared_ptr<FillCollector<YAO>>;
0189     using YAO::operator =;
0190 
0191     FillCollector() : YAO() { }
0192 
0193     /// Constructor
0194     ///
0195     /// We call the cheaper constructor based on the
0196     /// binning to avoid copying of the bin content.
0197     /// The underlying binning object is still used
0198     /// in analize() by many routines, e.g. to query
0199     /// numBins() or to loop over bins() with
0200     /// subsequent calls to bin xEdge() etc.
0201     FillCollector(typename YAO::Ptr yao) : YAO(yao->binning()) {
0202       YAO::setPath(yao->path());
0203     }
0204 
0205     /// Overloaded fill method, which stores Fill info
0206     /// until Multiplexer<T>::collapseEventGroup() is called.
0207     ///
0208     /// @todo Do we need to deal with users using fractions directly?
0209     int fill(typename YAO::FillType&& fillCoords,
0210              const double weight=1.0, const double fraction=1.0) {
0211       (void)fraction; // suppress unused variable warning
0212       YAO::icurr = YAO::_binning.globalIndexAt(fillCoords);
0213       _fills.insert(_fills.end(), { std::move(fillCoords), weight } );
0214       return (int)YAO::icurr;
0215     }
0216 
0217     int fill(const std::string val, const double weight = 1.0, const double fraction = 1.0) {
0218       return fill(typename YAO::FillType{val}, weight, fraction);
0219     }
0220 
0221     int fillnext(const bool cutresult, const double weight = 1.0, const double fraction = 1.0) {
0222       if (!cutresult)  return 0;
0223       return fill(YAO::bin(++YAO::icurr).xEdge(), weight, fraction);
0224     }
0225 
0226     int fillnext(const double weight = 1.0, const double fraction = 1.0) {
0227       return fillnext(true, weight, fraction);
0228     }
0229 
0230     int fillnext(const std::vector<bool>& cutresults, const double weight = 1.0, const double fraction = 1.0) {
0231       if (YAO::icurr+cutresults.size() > YAO::numBins()+1) {
0232         throw RangeError("Number of filled cut results needs to match the Cutflow construction (in cutflow '"+YAO::path()+"')");
0233       }
0234       for (size_t i = 0; i < cutresults.size(); ++i) {
0235         if (!cutresults[i])  return 0;
0236         fillnext(weight, fraction);
0237       }
0238       return (int)YAO::icurr;
0239     }
0240 
0241     int fillinit(const double weight=1.0, const double fraction=1.0) {
0242       return fill(""s, weight, fraction);
0243     }
0244 
0245     /// Empty the subevent stack (for start of new event group).
0246     void reset() noexcept { _fills.clear(); }
0247 
0248     /// Access the fill info subevent stack.
0249     const Fills<YAO>& fills() const { return _fills; }
0250 
0251   private:
0252 
0253     Fills<YAO> _fills;
0254 
0255   };
0256 
0257 
0258   /// @name Cutflow utilities
0259   /// @{
0260 
0261   /// Convenience alias
0262   using CutflowPtr = MultiplexPtr<Multiplexer<Cutflow>>;
0263 
0264   /// Print a Cutflow to a stream
0265   inline std::ostream& operator << (std::ostream& os, const Cutflow& cf) {
0266     return os << cf.str();
0267   }
0268 
0269   inline std::ostream& operator << (std::ostream& os, const CutflowPtr& cf) {
0270     return os << cf->str();
0271   }
0272 
0273   /// @}
0274 
0275 
0276   /// A container for several Cutflow objects, with some convenient batch access
0277   class Cutflows : public YODA::FillableStorage<2, CutflowPtr, std::string>,
0278                      public YODA::Fillable {
0279   public:
0280 
0281     using BaseT = YODA::FillableStorage<2, CutflowPtr, std::string>;
0282     using BinContentT = CutflowPtr;
0283     using BinT = YODA::Bin<1, BinContentT, BaseT::BinningT>;
0284     using FillDim = std::integral_constant<size_t, 2>;
0285 
0286     /// @name Constructors
0287     /// @{
0288 
0289     /// Nullary constructor
0290     Cutflows() : BaseT(groupAdapter<FillDim{}, BinContentT, std::string>) { }
0291 
0292     /// Constructor using a vector of (outer) edges
0293     Cutflows(const std::vector<std::string>& edges)
0294          : BaseT(YODA::Axis<std::string>(edges), groupAdapter<FillDim{}, BinContentT, std::string>) { }
0295 
0296     /// Constructor using an initializer_set of (outer) edges
0297     Cutflows(std::initializer_list<std::string>&& edges)
0298          : BaseT(YODA::Axis<std::string>(std::move(edges)), groupAdapter<FillDim{}, BinContentT, std::string>) { }
0299 
0300     /// @}
0301 
0302     /// @name Fill methods
0303     /// @{
0304 
0305     /// Fill method using the FillType of the underlying FIllableStorage
0306     int fill(const std::string& grpCoord, const std::string& edge, const double weight = 1.0, const double fraction = 1.0) {
0307       auto& cfl = BaseT::binAt({grpCoord});
0308       if (!cfl.raw())  return -1; // nullptr if bin not booked
0309       return cfl->fill({edge}, weight, fraction);
0310     }
0311 
0312     /// Method to fill the pre-cut steps
0313     int groupfillinit(const double weight=1.0, const double fraction=1.0) {
0314       bool pass = true;
0315       for (auto& cfl : BaseT::bins()) {
0316         if (!cfl.get())  continue;
0317         pass &= cfl->fillinit(weight, fraction);
0318       }
0319       return (int)pass;
0320     }
0321 
0322     /// Method to fill the next step if @a cutresult is true
0323     int groupfillnext(const bool cutresult, const double weight = 1.0, const double fraction = 1.0) {
0324       if (!cutresult)  return 0;
0325       bool pass = true;
0326       for (auto& cfl : BaseT::bins()) {
0327         if (!cfl.get())  continue;
0328         pass &= cfl->fillnext(cutresult, weight, fraction);
0329       }
0330       return (int)pass;
0331     }
0332 
0333     /// Short-hand method that assumes the cut result is true
0334     int groupfillnext(const double weight = 1.0, const double fraction = 1.0) {
0335       return groupfillnext(true, weight, fraction);
0336     }
0337 
0338     /// Method using a vector of @a cutresults
0339     int groupfillnext(const std::vector<bool>& cutresults, const double weight = 1.0, const double fraction = 1.0) {
0340       bool pass = true;
0341       for (auto& cfl : BaseT::bins()) {
0342         if (!cfl.get())  continue;
0343         pass &= cfl->fillnext(cutresults, weight, fraction);
0344       }
0345       return (int)pass;
0346     }
0347 
0348     /// Method that only fills the next step at coordinate @a grpCoord
0349     int fillnext(const std::string& grpCoord, const double weight=1.0, const double fraction=1.0) {
0350       return BaseT::binAt(grpCoord)->fillnext(weight, fraction);
0351     }
0352 
0353     /// Method that only fills the next step at coordinate @a grpCoord if @a cutresult is true
0354     int fillnext(const std::string& grpCoord, const bool cutresult, const double weight=1.0, const double fraction=1.0) {
0355       return BaseT::binAt(grpCoord)->fillnext(cutresult, weight, fraction);
0356     }
0357 
0358     /// Method that only fills the next step at coordinate @a grpCoord using a vector of @a cutresults
0359     int fillnext(const std::string& grpCoord, const std::vector<bool>& cutresults,
0360                  const double weight = 1.0, const double fraction = 1.0) {
0361       return BaseT::binAt(grpCoord)->fillnext(cutresults, weight, fraction);
0362     }
0363 
0364     /// @}
0365 
0366     /// @name Reset methods
0367     /// @{
0368 
0369     /// @brief Reset the histogram.
0370     ///
0371     /// Keep the binning but set all bin contents and related quantities to zero
0372     void reset() noexcept { BaseT::reset(); }
0373 
0374     /// @}
0375 
0376     /// @name Whole histo data
0377     /// @{
0378 
0379     /// @brief Fill dimension of the object (number of conent axes + temprary axis)
0380     size_t fillDim() const noexcept { return FillDim{}; }
0381 
0382     /// @brief Total dimension of the object (number of fill axes + filled value)
0383     size_t dim() const noexcept { return fillDim()+1; }
0384 
0385     /// @brief Get the number of fills (fractional fills are possible).
0386     double numEntries(const bool includeOverflows=true) const noexcept {
0387       double n = 0;
0388       for (const auto& cfl : BaseT::bins(includeOverflows)) {
0389         if (!cfl.get())  continue;
0390         n += cfl->numEntries(includeOverflows);
0391       }
0392       return n;
0393     }
0394 
0395     /// @brief Get the effective number of fills.
0396     double effNumEntries(const bool includeOverflows=true) const noexcept {
0397       double n = 0;
0398       for (const auto& cfl : BaseT::bins(includeOverflows)) {
0399         if (!cfl.get())  continue;
0400         n += cfl->effNumEntries(includeOverflows);
0401       }
0402       return n;
0403     }
0404 
0405     /// @brief Calculates sum of weights in histo group.
0406     double sumW(const bool includeOverflows=true) const noexcept {
0407       double sumw = 0;
0408       for (const auto& cfl : BaseT::bins(includeOverflows)) {
0409         if (!cfl.get())  continue;
0410         sumw += cfl->sumW(includeOverflows);
0411       }
0412       return sumw;
0413     }
0414 
0415     /// @brief Calculates sum of squared weights in histo group.
0416     double sumW2(const bool includeOverflows=true) const noexcept {
0417       double sumw2 = 0;
0418       for (const auto& cfl : BaseT::bins(includeOverflows)) {
0419         if (!cfl.get())  continue;
0420         sumw2 += cfl->sumW2(includeOverflows);
0421       }
0422       return sumw2;
0423     }
0424 
0425     /// @brief Get the total volume of the histogram group.
0426     double integral(const bool includeOverflows=true) const noexcept {
0427       return sumW(includeOverflows);
0428     }
0429 
0430     /// @brief Get the total volume error of the histogram group.
0431     double integralError(const bool includeOverflows=true) const noexcept {
0432       return sqrt(sumW2(includeOverflows));
0433     }
0434 
0435     /// @}
0436 
0437     /// @name Transformations
0438     /// @{
0439 
0440     /// @brief Rescale as if all fill weights had been different by factor @a scalefactor.
0441     void scaleW(const double scalefactor) noexcept {
0442       for (auto& cfl : BaseT::bins(true, true)) {
0443         if (!cfl.get())  continue;
0444         cfl->scaleW(scalefactor);
0445       }
0446     }
0447 
0448     /// @brief Rescale as if all fill weights had been different by factor @a scalefactor along dimension @a i.
0449     void scale(const double scalefactor) noexcept {
0450       for (auto& cfl : BaseT::bins(true, true)) {
0451         if (!cfl.get())  continue;
0452         cfl->scale(scalefactor);
0453       }
0454     }
0455 
0456     /// Scale the cutflow weights so that the weight count after cut @a edge is @a norm
0457     ///
0458     /// @note Consider each sub-histogram separately when calculating the integral.
0459     void normalizeStep(const std::string& edge, const double norm) {
0460       for (auto& cfl : BaseT::bins(true, true)) {
0461         if (!cfl.get())  continue;
0462         if (cfl->binAt(edge).sumW() != 0.)  cfl->normalizeStep(edge, norm);
0463       }
0464     }
0465 
0466     /// Alias to scale the cutflow weights so that the weight after the pre-init cut is @a norm
0467     void normalizeFirst(const double norm) {
0468       normalizeStep(""s, norm);
0469     }
0470 
0471     /// @}
0472 
0473     /// @name Streaming utilities
0474     /// @{
0475 
0476     /// Create a string representation
0477     string str() const {
0478       std::stringstream ss;
0479       for (auto& cf : BaseT::bins(true, true)) {
0480         if (!cf.get())  continue;
0481         ss << cf << "\n\n";
0482       }
0483       return ss.str();
0484     }
0485 
0486     /// Print string representation to a stream
0487     void print(std::ostream& os) const {
0488       os << str() << std::flush;
0489     }
0490 
0491     /// @}
0492 
0493   };
0494 
0495   /// @name Cutflows utilities
0496   /// @{
0497 
0498   /// Convenience alias
0499   using CutflowsPtr = std::shared_ptr<Cutflows>;
0500 
0501   /// Print a Cutflows to a stream
0502   inline std::ostream& operator << (std::ostream& os, const Cutflows& cfs) {
0503     return os << cfs.str();
0504   }
0505 
0506   /// Print a Cutflows to a stream
0507   inline std::ostream& operator << (std::ostream& os, const CutflowsPtr& cfs) {
0508     return os << cfs->str();
0509   }
0510 
0511   /// @}
0512 
0513 }
0514 
0515 #endif