From 974cd0f1b96544d6e9c3d33d8522f0ff9f35b23f Mon Sep 17 00:00:00 2001 From: moritzmolch Date: Tue, 5 May 2026 11:23:21 +0200 Subject: [PATCH 01/20] Add function to calculate b tagging SFs with a multiple WP setup --- src/jets.cxx | 250 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) diff --git a/src/jets.cxx b/src/jets.cxx index 107af811..f7505a81 100644 --- a/src/jets.cxx +++ b/src/jets.cxx @@ -1844,6 +1844,256 @@ BtaggingWP(ROOT::RDF::RNode df, return df2; } +/** + * @brief This function calculates the b-tagging scale factor. The scale + * factor corrects inconsistencies in the b-tagging efficiency between data and + * simulation. The scale factors are loaded from a correctionlib file + * using a specified scale factor name and variation. + * + * This producer can be used to evaluate working point based scale factors. It is + * defined based on scale factors provided by BTV POG for Run3 2024. + * + * More information from BTV POG can be found here https://btv-wiki.docs.cern.ch/ScaleFactors/ + * + * @param df input dataframe + * @param correction_manager correction manager responsible for loading the + * correction file + * @param outputname name of the output column containing the b-tagging scale factor + * @param pt name of the column containing the transverse momenta of jets + * @param eta name of the column containing the pseudorapidity of jets + * @param flavor name of the column containing the flavors of jets, usually used + * flavors are: 5=b-jet, 4=c-jet, 0=light jet (g, u, d, s) + * @param jet_mask name of the column containing the mask for good/selected jets + * @param bjet_mask name of the column containing the mask for good/selected b-jets + * @param jet_veto_mask name of the column containing the veto mask for + * overlapping jets (e.g. with selected lepton pairs) + * @param sf_file path to the file with the b-tagging scale factors + * @param sf_name name of the b-tagging scale factor correction e.g. "deepJet_shape" + * @param variation name the scale factor variation, available values: + * central, down_*, up_* (* name of specific variation) + * @param btag_wp string that specifies the b-tagging working point used in an + * analysis e.g. "L", "M", "T", ... + * + * @return a new dataframe containing the new column + */ +ROOT::RDF::RNode +BtaggingMultipleWP( + ROOT::RDF::RNode df, + correctionManager::CorrectionManager &correction_manager, + const std::string &outputname, + const std::string &pt, + const std::string &eta, + const std::string &btag_value, + const std::string &flavor, + const std::string &jet_mask, + const std::string &bjet_mask, + const std::string &jet_veto_mask, + const std::string &sf_file, + const std::string &sf_name, + const std::string &sf_wp_name, + const std::string &eff_file, + const std::string &eff_name, + const std::string &variation, + const std::string &btag_wp +) { + // Set the logger name for better readability in debug messages + const std::string logger_name = + "physicsobject::jet::scalefactor::BtaggingMultipleWP"; + + // Debug messages for loading corrections + Logger::get(logger_name)->debug( + "Setting up functions for multiple WP setup with correctionlib" + ); + Logger::get(logger_name)->debug("correction cset name {}", sf_name); + Logger::get(logger_name)->debug("working point cset name {}", sf_wp_name); + Logger::get(logger_name)->debug("efficiency cset name {}", eff_name); + + // Get evaluators for SF, WP definitions, and from correctionlib files + auto sf_evaluator = correction_manager.loadCorrection(sf_file, sf_name); + auto wp_evaluator = correction_manager.loadCorrection(sf_file, sf_wp_name); + auto sf_evaluator = correction_manager.loadCorrection(eff_file, eff_name); + + // Define a map between the b-tagging working point name and the + // corresponding discriminator cut value. A custom value 'N' is used in the + // case the jet does not pass the loosest working point. + std::map btag_wp_map; + for (const auto& wp : {"T", "M", "L"}) { + btag_wp_map[wp] = wp_evaluator->evaluate({wp}); + }; + btag_wp_map["N"] = -10.0; + + // In nanoAODv12 the type of jet flavor was changed to UChar_t + // For v9 compatibility a type casting is applied + auto [df1, flavor_column_v12] = utility::Cast< + ROOT::RVec, + ROOT::RVec + >(df, flavor+"_v12", "ROOT::VecOps::RVec", flavor); + + auto b_tagging_sf = [ + eff_evaluator, + sf_evaluator, + btag_wp_map, + variation, + logger_name + ]( + const ROOT::RVec &etas, + const ROOT::RVec &pts, + const ROOT::RVec &btag_value, + const ROOT::RVec &flavors_v12, + const ROOT::RVec &jet_mask, + const ROOT::RVec &bjet_mask, + const ROOT::RVec &jet_veto_mask + ) { + + Logger::get(logger_name)->debug( + "calculate b jet tagging event weight in multiple WP setup" + ); + Logger::get(logger_name)->debug("variation name {}", variation); + + // Define the event scale factor + float sf = 1.0; + + // Cast flavor column to integers + auto flavors = static_cast>(flavors_v12); + + for (int i = 0; i < pts.size(); i++) { + Logger::get(logger_name)->debug( + "SF - pt {}, eta {}, b tagging score {}, flavor {}", + pts.at(i), + etas.at(i), + btag_value.at(i), + flavors.at(i) + ); + + // Skip jets that do not pass the jet/b jet selection + if (! + ( + (jet_mask.at(i) || bjet_mask.at(i)) + && jet_veto_mask.at(i) + ) + ) { + continue; + } + + // Get the passed b jet tagging working point for this jet + // A custom value 'N' is used in the case the jet does not pass + // the loosest working point for the given b jet tagging algorithm. + std::string btag_wp = "N"; + for (const auto& [wp, cut] : btag_wp_map) { + if (btag_value.at(i) >= cut) { + btag_wp = wp; + break; + } + } + Logger::get(logger_name)->debug( + "b tagging score {}, passes WP {}", btag_value.at(i), btag_wp + ); + + // Define list of b tagging working point scale factors needed for + // this jet + std::vector wps = {}; + if (btag_wp == "T") { + wps = {"T"}; + } else if (btag_wp == "M") { + wps = {"M", "T"}; + } else if (btag_wp == "L") { + wps = {"L", "M"}; + } else { + wps = {"L"}; + } + + // Obtain scale factors in the phase space where they are + // well-defined + std::map jet_eff; + std::map jet_sf; + for (const auto& wp : wps) { + if ( + pts.at(i) >= 20.0 + && pts.at(i) < 10000.0 + && std::abs(etas.at(i)) < 2.5 + && btag_value.at(i) >= 0 + ) { + jet_sf[wp] = sf_evaluator->evaluate({ + variation, + wp, + flavors.at(i), + std::abs(etas.at(i)), + pts.at(i) + }); + jet_eff[wp] = eff_evaluator->evaluate({ + wp, + flavors.at(i), + std::abs(etas.at(i)), + pts.at(i) + }); + } else { + jet_sf[wp] = 1.0; + jet_eff[wp] = 1.0; + } + } + Logger::get(logger_name)->debug("got SFs {}", jet_sf); + Logger::get(logger_name)->debug("got efficiencies {}", jet_eff); + + // Multiply this jet's contribution to the event scale factor based + // on the working point it passes. + float jet_comp = 1.0; + if (btag_wp == "T") { + jet_comp = jet_sf["T"]; + } else if (btag_wp == "M") { + jet_comp = ( + jet_sf["M"] * jet_eff["M"] - jet_sf["T"] * jet_eff["T"] + ) / ( + jet_eff["M"] - jet_eff["T"] + ); + } else if (btag_wp == "L") { + jet_comp = ( + jet_sf["L"] * jet_eff["L"] - jet_sf["M"] * jet_eff["M"] + ) / ( + jet_eff["L"] - jet_eff["M"] + ); + } else if (btag_wp == "N") { + jet_comp = ( + 1.0 - jet_sf["L"] * jet_eff["L"]) / (1.0 - jet_eff["L"] + ); + } else { + Logger::get(logger_name)->error( + "Arrived at unexpected b tagging working point {}", btag_wp + ); + throw std::runtime_error(); + } + + // Debug message for this jet's contribution to the event scale + // factor + Logger::get(logger_name)->debug( + "Jet contribution to event b tagging SF {}", jet_comp + ); + + // Multiply the jet's contribution to the event scale factor + sf *= jet_comp; + }; + + // Debug message for event scale factor after all jets have been + // processed + Logger::get(logger_name)->debug("Event Scale Factor {}", sf); + + return sf; + }; + + return df1.Define( + outputname, + b_tagging_sf, + { + pt, + eta, + btag_value, + flavor_column_v12, + jet_mask, + bjet_mask, + jet_veto_mask + } + ); +} + } // end namespace scalefactor } // end namespace jet } // end namespace physicsobject From d62b2f03b18521a2ae513c5c23215e8783eb2ca7 Mon Sep 17 00:00:00 2001 From: moritzmolch Date: Tue, 5 May 2026 11:32:17 +0200 Subject: [PATCH 02/20] Fix some smaller bugs --- src/jets.cxx | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/jets.cxx b/src/jets.cxx index f7505a81..a6dd2f46 100644 --- a/src/jets.cxx +++ b/src/jets.cxx @@ -1893,8 +1893,7 @@ BtaggingMultipleWP( const std::string &sf_wp_name, const std::string &eff_file, const std::string &eff_name, - const std::string &variation, - const std::string &btag_wp + const std::string &variation ) { // Set the logger name for better readability in debug messages const std::string logger_name = @@ -1911,16 +1910,19 @@ BtaggingMultipleWP( // Get evaluators for SF, WP definitions, and from correctionlib files auto sf_evaluator = correction_manager.loadCorrection(sf_file, sf_name); auto wp_evaluator = correction_manager.loadCorrection(sf_file, sf_wp_name); - auto sf_evaluator = correction_manager.loadCorrection(eff_file, eff_name); + auto eff_evaluator = correction_manager.loadCorrection(eff_file, eff_name); // Define a map between the b-tagging working point name and the // corresponding discriminator cut value. A custom value 'N' is used in the - // case the jet does not pass the loosest working point. - std::map btag_wp_map; - for (const auto& wp : {"T", "M", "L"}) { - btag_wp_map[wp] = wp_evaluator->evaluate({wp}); + // case the jet does not pass the loosest working point. Note that the list + // of working point names must be ordered from the tightest to the loosest + // one. + std::vector wp_names = {"T", "M", "L"}; + std::map wp_map; + for (const auto& wp : wp_names) { + wp_map[wp] = wp_evaluator->evaluate({wp}); }; - btag_wp_map["N"] = -10.0; + wp_map["N"] = -10.0; // In nanoAODv12 the type of jet flavor was changed to UChar_t // For v9 compatibility a type casting is applied @@ -1932,7 +1934,8 @@ BtaggingMultipleWP( auto b_tagging_sf = [ eff_evaluator, sf_evaluator, - btag_wp_map, + wp_map, + wp_names, variation, logger_name ]( @@ -1979,8 +1982,8 @@ BtaggingMultipleWP( // A custom value 'N' is used in the case the jet does not pass // the loosest working point for the given b jet tagging algorithm. std::string btag_wp = "N"; - for (const auto& [wp, cut] : btag_wp_map) { - if (btag_value.at(i) >= cut) { + for (const auto& wp : btag_wp_names) { + if (btag_value.at(i) >= wp_map.at(wp)) { btag_wp = wp; break; } @@ -2059,7 +2062,9 @@ BtaggingMultipleWP( Logger::get(logger_name)->error( "Arrived at unexpected b tagging working point {}", btag_wp ); - throw std::runtime_error(); + throw std::runtime_error( + "Arrived at unexpected b tagging working point " + btag_wp + ); } // Debug message for this jet's contribution to the event scale @@ -2074,7 +2079,7 @@ BtaggingMultipleWP( // Debug message for event scale factor after all jets have been // processed - Logger::get(logger_name)->debug("Event Scale Factor {}", sf); + Logger::get(logger_name)->debug("event scale factor: {}", sf); return sf; }; From 3adf115198a2af0c294244a817416a0780fe2463 Mon Sep 17 00:00:00 2001 From: moritzmolch Date: Tue, 5 May 2026 11:43:18 +0200 Subject: [PATCH 03/20] Update documentation --- src/jets.cxx | 46 ++++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/jets.cxx b/src/jets.cxx index a6dd2f46..09684b54 100644 --- a/src/jets.cxx +++ b/src/jets.cxx @@ -1845,34 +1845,40 @@ BtaggingWP(ROOT::RDF::RNode df, } /** - * @brief This function calculates the b-tagging scale factor. The scale - * factor corrects inconsistencies in the b-tagging efficiency between data and + * @brief This function calculates the event b jet tagging scale factor for + * a setup with multiple working points. The scale factor corrects + * inconsistencies in the b-tagging efficiency between data and * simulation. The scale factors are loaded from a correctionlib file - * using a specified scale factor name and variation. + * using a specified scale factor name and variation. In addition, tagging + * efficiencies of the jets are loaded from a separate correctionlib file to be + * able to apply the appropriate scale factor. * - * This producer can be used to evaluate working point based scale factors. It is - * defined based on scale factors provided by BTV POG for Run3 2024. - * - * More information from BTV POG can be found here https://btv-wiki.docs.cern.ch/ScaleFactors/ + * The procedure follows the recommendations of the BTV group: + * https://btv-wiki.docs.cern.ch/PerformanceCalibration/fixedWPSFRecommendations/#scale-factor-recommendations-for-event-reweighting * * @param df input dataframe * @param correction_manager correction manager responsible for loading the - * correction file - * @param outputname name of the output column containing the b-tagging scale factor + * correction file + * @param outputname name of the output column containing the b-tagging scale + * factor * @param pt name of the column containing the transverse momenta of jets * @param eta name of the column containing the pseudorapidity of jets + * @param btag_value name of the column containing the btag scores of jets * @param flavor name of the column containing the flavors of jets, usually used - * flavors are: 5=b-jet, 4=c-jet, 0=light jet (g, u, d, s) + * flavors are: 5=b-jet, 4=c-jet, 0=light jet (g, u, d, s) * @param jet_mask name of the column containing the mask for good/selected jets - * @param bjet_mask name of the column containing the mask for good/selected b-jets + * @param bjet_mask name of the column containing the mask for good/selected + * b-jets * @param jet_veto_mask name of the column containing the veto mask for - * overlapping jets (e.g. with selected lepton pairs) + * overlapping jets (e.g. with selected lepton pairs) * @param sf_file path to the file with the b-tagging scale factors - * @param sf_name name of the b-tagging scale factor correction e.g. "deepJet_shape" + * @param sf_name name of the b-tagging scale factor correction + * @param sf_wp_name name of the correction set containing the b tagging score + * cuts for the different working points + * @param eff_file path to the file with the b jet tagging efficiencies + * @param eff_name name of the b jet tagging efficiency correction set * @param variation name the scale factor variation, available values: - * central, down_*, up_* (* name of specific variation) - * @param btag_wp string that specifies the b-tagging working point used in an - * analysis e.g. "L", "M", "T", ... + * central, down_*, up_* (* name of specific variation) * * @return a new dataframe containing the new column */ @@ -2067,6 +2073,14 @@ BtaggingMultipleWP( ); } + // Force the SF to a positive value, set it to unity otherwise + if (jet_comp < 0.0) { + Logger::get(logger_name)->debug( + "got negative jet contribution {}, set it to 1.0", jet_comp + ); + jet_comp = 1.0; + } + // Debug message for this jet's contribution to the event scale // factor Logger::get(logger_name)->debug( From b0215bbc13b72ca1f1c5f54c3c4af03d79b5a370 Mon Sep 17 00:00:00 2001 From: moritzmolch Date: Tue, 5 May 2026 11:44:43 +0200 Subject: [PATCH 04/20] Add BtaggingMultipleWP to header file --- include/jets.hxx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/include/jets.hxx b/include/jets.hxx index d5180638..b63f6fd6 100644 --- a/include/jets.hxx +++ b/include/jets.hxx @@ -141,6 +141,25 @@ BtaggingWP(ROOT::RDF::RNode df, const std::string &jet_veto_mask, const std::string &sf_file, const std::string &sf_name, const std::string &variation, const std::string &btag_wp); +ROOT::RDF::RNode +BtaggingMultipleWP( + ROOT::RDF::RNode df, + correctionManager::CorrectionManager &correction_manager, + const std::string &outputname, + const std::string &pt, + const std::string &eta, + const std::string &btag_value, + const std::string &flavor, + const std::string &jet_mask, + const std::string &bjet_mask, + const std::string &jet_veto_mask, + const std::string &sf_file, + const std::string &sf_name, + const std::string &sf_wp_name, + const std::string &eff_file, + const std::string &eff_name, + const std::string &variation +); } // end namespace scalefactor } // end namespace jet } // end namespace physicsobject From e51363e845aec4f412439a86b6f75eb32da22715 Mon Sep 17 00:00:00 2001 From: moritzmolch Date: Tue, 5 May 2026 12:19:06 +0200 Subject: [PATCH 05/20] Fix bugs --- src/jets.cxx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/jets.cxx b/src/jets.cxx index 09684b54..72aa756a 100644 --- a/src/jets.cxx +++ b/src/jets.cxx @@ -1988,7 +1988,7 @@ BtaggingMultipleWP( // A custom value 'N' is used in the case the jet does not pass // the loosest working point for the given b jet tagging algorithm. std::string btag_wp = "N"; - for (const auto& wp : btag_wp_names) { + for (const auto& wp : wp_names) { if (btag_value.at(i) >= wp_map.at(wp)) { btag_wp = wp; break; @@ -2039,9 +2039,13 @@ BtaggingMultipleWP( jet_sf[wp] = 1.0; jet_eff[wp] = 1.0; } + Logger::get(logger_name)->debug( + "got SFs {} (WP {})", jet_sf[wp], wp + ); + Logger::get(logger_name)->debug( + "got efficiencies {} (WP {})", jet_eff[wp], wp + ); } - Logger::get(logger_name)->debug("got SFs {}", jet_sf); - Logger::get(logger_name)->debug("got efficiencies {}", jet_eff); // Multiply this jet's contribution to the event scale factor based // on the working point it passes. From a5e2ca7729965f4c5a2311c1c01035a36c3ae85b Mon Sep 17 00:00:00 2001 From: moritzmolch Date: Tue, 5 May 2026 12:57:42 +0200 Subject: [PATCH 06/20] clang format --- include/jets.hxx | 28 +++---- src/jets.cxx | 205 +++++++++++++++++------------------------------ 2 files changed, 84 insertions(+), 149 deletions(-) diff --git a/include/jets.hxx b/include/jets.hxx index b63f6fd6..87a9f446 100644 --- a/include/jets.hxx +++ b/include/jets.hxx @@ -142,24 +142,16 @@ BtaggingWP(ROOT::RDF::RNode df, const std::string &sf_name, const std::string &variation, const std::string &btag_wp); ROOT::RDF::RNode -BtaggingMultipleWP( - ROOT::RDF::RNode df, - correctionManager::CorrectionManager &correction_manager, - const std::string &outputname, - const std::string &pt, - const std::string &eta, - const std::string &btag_value, - const std::string &flavor, - const std::string &jet_mask, - const std::string &bjet_mask, - const std::string &jet_veto_mask, - const std::string &sf_file, - const std::string &sf_name, - const std::string &sf_wp_name, - const std::string &eff_file, - const std::string &eff_name, - const std::string &variation -); +BtaggingMultipleWP(ROOT::RDF::RNode df, + correctionManager::CorrectionManager &correction_manager, + const std::string &outputname, const std::string &pt, + const std::string &eta, const std::string &btag_value, + const std::string &flavor, const std::string &jet_mask, + const std::string &bjet_mask, + const std::string &jet_veto_mask, const std::string &sf_file, + const std::string &sf_name, const std::string &sf_wp_name, + const std::string &eff_file, const std::string &eff_name, + const std::string &variation); } // end namespace scalefactor } // end namespace jet } // end namespace physicsobject diff --git a/src/jets.cxx b/src/jets.cxx index 72aa756a..6f4977bd 100644 --- a/src/jets.cxx +++ b/src/jets.cxx @@ -1845,10 +1845,10 @@ BtaggingWP(ROOT::RDF::RNode df, } /** - * @brief This function calculates the event b jet tagging scale factor for + * @brief This function calculates the event b jet tagging scale factor for * a setup with multiple working points. The scale factor corrects * inconsistencies in the b-tagging efficiency between data and - * simulation. The scale factors are loaded from a correctionlib file + * simulation. The scale factors are loaded from a correctionlib file * using a specified scale factor name and variation. In addition, tagging * efficiencies of the jets are loaded from a separate correctionlib file to be * able to apply the appropriate scale factor. @@ -1869,7 +1869,7 @@ BtaggingWP(ROOT::RDF::RNode df, * @param jet_mask name of the column containing the mask for good/selected jets * @param bjet_mask name of the column containing the mask for good/selected * b-jets - * @param jet_veto_mask name of the column containing the veto mask for + * @param jet_veto_mask name of the column containing the veto mask for * overlapping jets (e.g. with selected lepton pairs) * @param sf_file path to the file with the b-tagging scale factors * @param sf_name name of the b-tagging scale factor correction @@ -1883,32 +1883,24 @@ BtaggingWP(ROOT::RDF::RNode df, * @return a new dataframe containing the new column */ ROOT::RDF::RNode -BtaggingMultipleWP( - ROOT::RDF::RNode df, - correctionManager::CorrectionManager &correction_manager, - const std::string &outputname, - const std::string &pt, - const std::string &eta, - const std::string &btag_value, - const std::string &flavor, - const std::string &jet_mask, - const std::string &bjet_mask, - const std::string &jet_veto_mask, - const std::string &sf_file, - const std::string &sf_name, - const std::string &sf_wp_name, - const std::string &eff_file, - const std::string &eff_name, - const std::string &variation -) { +BtaggingMultipleWP(ROOT::RDF::RNode df, + correctionManager::CorrectionManager &correction_manager, + const std::string &outputname, const std::string &pt, + const std::string &eta, const std::string &btag_value, + const std::string &flavor, const std::string &jet_mask, + const std::string &bjet_mask, + const std::string &jet_veto_mask, const std::string &sf_file, + const std::string &sf_name, const std::string &sf_wp_name, + const std::string &eff_file, const std::string &eff_name, + const std::string &variation) { // Set the logger name for better readability in debug messages const std::string logger_name = "physicsobject::jet::scalefactor::BtaggingMultipleWP"; // Debug messages for loading corrections - Logger::get(logger_name)->debug( - "Setting up functions for multiple WP setup with correctionlib" - ); + Logger::get(logger_name) + ->debug( + "Setting up functions for multiple WP setup with correctionlib"); Logger::get(logger_name)->debug("correction cset name {}", sf_name); Logger::get(logger_name)->debug("working point cset name {}", sf_wp_name); Logger::get(logger_name)->debug("efficiency cset name {}", eff_name); @@ -1925,38 +1917,29 @@ BtaggingMultipleWP( // one. std::vector wp_names = {"T", "M", "L"}; std::map wp_map; - for (const auto& wp : wp_names) { + for (const auto &wp : wp_names) { wp_map[wp] = wp_evaluator->evaluate({wp}); }; wp_map["N"] = -10.0; // In nanoAODv12 the type of jet flavor was changed to UChar_t // For v9 compatibility a type casting is applied - auto [df1, flavor_column_v12] = utility::Cast< - ROOT::RVec, - ROOT::RVec - >(df, flavor+"_v12", "ROOT::VecOps::RVec", flavor); - - auto b_tagging_sf = [ - eff_evaluator, - sf_evaluator, - wp_map, - wp_names, - variation, - logger_name - ]( - const ROOT::RVec &etas, - const ROOT::RVec &pts, - const ROOT::RVec &btag_value, - const ROOT::RVec &flavors_v12, - const ROOT::RVec &jet_mask, - const ROOT::RVec &bjet_mask, - const ROOT::RVec &jet_veto_mask - ) { - - Logger::get(logger_name)->debug( - "calculate b jet tagging event weight in multiple WP setup" - ); + auto [df1, flavor_column_v12] = + utility::Cast, ROOT::RVec>( + df, flavor + "_v12", "ROOT::VecOps::RVec", flavor); + + auto b_tagging_sf = [eff_evaluator, sf_evaluator, wp_map, wp_names, + variation, + logger_name](const ROOT::RVec &etas, + const ROOT::RVec &pts, + const ROOT::RVec &btag_value, + const ROOT::RVec &flavors_v12, + const ROOT::RVec &jet_mask, + const ROOT::RVec &bjet_mask, + const ROOT::RVec &jet_veto_mask) { + Logger::get(logger_name) + ->debug( + "calculate b jet tagging event weight in multiple WP setup"); Logger::get(logger_name)->debug("variation name {}", variation); // Define the event scale factor @@ -1966,21 +1949,12 @@ BtaggingMultipleWP( auto flavors = static_cast>(flavors_v12); for (int i = 0; i < pts.size(); i++) { - Logger::get(logger_name)->debug( - "SF - pt {}, eta {}, b tagging score {}, flavor {}", - pts.at(i), - etas.at(i), - btag_value.at(i), - flavors.at(i) - ); + Logger::get(logger_name) + ->debug("SF - pt {}, eta {}, b tagging score {}, flavor {}", + pts.at(i), etas.at(i), btag_value.at(i), flavors.at(i)); // Skip jets that do not pass the jet/b jet selection - if (! - ( - (jet_mask.at(i) || bjet_mask.at(i)) - && jet_veto_mask.at(i) - ) - ) { + if (!((jet_mask.at(i) || bjet_mask.at(i)) && jet_veto_mask.at(i))) { continue; } @@ -1988,15 +1962,15 @@ BtaggingMultipleWP( // A custom value 'N' is used in the case the jet does not pass // the loosest working point for the given b jet tagging algorithm. std::string btag_wp = "N"; - for (const auto& wp : wp_names) { + for (const auto &wp : wp_names) { if (btag_value.at(i) >= wp_map.at(wp)) { btag_wp = wp; break; } } - Logger::get(logger_name)->debug( - "b tagging score {}, passes WP {}", btag_value.at(i), btag_wp - ); + Logger::get(logger_name) + ->debug("b tagging score {}, passes WP {}", btag_value.at(i), + btag_wp); // Define list of b tagging working point scale factors needed for // this jet @@ -2015,36 +1989,22 @@ BtaggingMultipleWP( // well-defined std::map jet_eff; std::map jet_sf; - for (const auto& wp : wps) { - if ( - pts.at(i) >= 20.0 - && pts.at(i) < 10000.0 - && std::abs(etas.at(i)) < 2.5 - && btag_value.at(i) >= 0 - ) { - jet_sf[wp] = sf_evaluator->evaluate({ - variation, - wp, - flavors.at(i), - std::abs(etas.at(i)), - pts.at(i) - }); - jet_eff[wp] = eff_evaluator->evaluate({ - wp, - flavors.at(i), - std::abs(etas.at(i)), - pts.at(i) - }); + for (const auto &wp : wps) { + if (pts.at(i) >= 20.0 && pts.at(i) < 10000.0 && + std::abs(etas.at(i)) < 2.5 && btag_value.at(i) >= 0) { + jet_sf[wp] = sf_evaluator->evaluate( + {variation, wp, flavors.at(i), std::abs(etas.at(i)), + pts.at(i)}); + jet_eff[wp] = eff_evaluator->evaluate( + {wp, flavors.at(i), std::abs(etas.at(i)), pts.at(i)}); } else { jet_sf[wp] = 1.0; jet_eff[wp] = 1.0; } - Logger::get(logger_name)->debug( - "got SFs {} (WP {})", jet_sf[wp], wp - ); - Logger::get(logger_name)->debug( - "got efficiencies {} (WP {})", jet_eff[wp], wp - ); + Logger::get(logger_name) + ->debug("got SFs {} (WP {})", jet_sf[wp], wp); + Logger::get(logger_name) + ->debug("got efficiencies {} (WP {})", jet_eff[wp], wp); } // Multiply this jet's contribution to the event scale factor based @@ -2053,43 +2013,36 @@ BtaggingMultipleWP( if (btag_wp == "T") { jet_comp = jet_sf["T"]; } else if (btag_wp == "M") { - jet_comp = ( - jet_sf["M"] * jet_eff["M"] - jet_sf["T"] * jet_eff["T"] - ) / ( - jet_eff["M"] - jet_eff["T"] - ); + jet_comp = + (jet_sf["M"] * jet_eff["M"] - jet_sf["T"] * jet_eff["T"]) / + (jet_eff["M"] - jet_eff["T"]); } else if (btag_wp == "L") { - jet_comp = ( - jet_sf["L"] * jet_eff["L"] - jet_sf["M"] * jet_eff["M"] - ) / ( - jet_eff["L"] - jet_eff["M"] - ); + jet_comp = + (jet_sf["L"] * jet_eff["L"] - jet_sf["M"] * jet_eff["M"]) / + (jet_eff["L"] - jet_eff["M"]); } else if (btag_wp == "N") { - jet_comp = ( - 1.0 - jet_sf["L"] * jet_eff["L"]) / (1.0 - jet_eff["L"] - ); + jet_comp = + (1.0 - jet_sf["L"] * jet_eff["L"]) / (1.0 - jet_eff["L"]); } else { - Logger::get(logger_name)->error( - "Arrived at unexpected b tagging working point {}", btag_wp - ); + Logger::get(logger_name) + ->error("Arrived at unexpected b tagging working point {}", + btag_wp); throw std::runtime_error( - "Arrived at unexpected b tagging working point " + btag_wp - ); + "Arrived at unexpected b tagging working point " + btag_wp); } // Force the SF to a positive value, set it to unity otherwise if (jet_comp < 0.0) { - Logger::get(logger_name)->debug( - "got negative jet contribution {}, set it to 1.0", jet_comp - ); + Logger::get(logger_name) + ->debug("got negative jet contribution {}, set it to 1.0", + jet_comp); jet_comp = 1.0; } - // Debug message for this jet's contribution to the event scale + // Debug message for this jet's contribution to the event scale // factor - Logger::get(logger_name)->debug( - "Jet contribution to event b tagging SF {}", jet_comp - ); + Logger::get(logger_name) + ->debug("Jet contribution to event b tagging SF {}", jet_comp); // Multiply the jet's contribution to the event scale factor sf *= jet_comp; @@ -2102,19 +2055,9 @@ BtaggingMultipleWP( return sf; }; - return df1.Define( - outputname, - b_tagging_sf, - { - pt, - eta, - btag_value, - flavor_column_v12, - jet_mask, - bjet_mask, - jet_veto_mask - } - ); + return df1.Define(outputname, b_tagging_sf, + {pt, eta, btag_value, flavor_column_v12, jet_mask, + bjet_mask, jet_veto_mask}); } } // end namespace scalefactor From 087ab2a01ba17124695438fc9a2e52197cde56a5 Mon Sep 17 00:00:00 2001 From: moritzmolch Date: Tue, 5 May 2026 13:05:40 +0200 Subject: [PATCH 07/20] Add sample type as parameter to b tagging efficiency --- include/jets.hxx | 1 + src/jets.cxx | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/include/jets.hxx b/include/jets.hxx index 87a9f446..99d3ca85 100644 --- a/include/jets.hxx +++ b/include/jets.hxx @@ -151,6 +151,7 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, const std::string &jet_veto_mask, const std::string &sf_file, const std::string &sf_name, const std::string &sf_wp_name, const std::string &eff_file, const std::string &eff_name, + const std::string &sample_type, const std::string &variation); } // end namespace scalefactor } // end namespace jet diff --git a/src/jets.cxx b/src/jets.cxx index 6f4977bd..4164c3db 100644 --- a/src/jets.cxx +++ b/src/jets.cxx @@ -1892,6 +1892,7 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, const std::string &jet_veto_mask, const std::string &sf_file, const std::string &sf_name, const std::string &sf_wp_name, const std::string &eff_file, const std::string &eff_name, + const std::string &sample_type, const std::string &variation) { // Set the logger name for better readability in debug messages const std::string logger_name = @@ -1929,7 +1930,7 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, df, flavor + "_v12", "ROOT::VecOps::RVec", flavor); auto b_tagging_sf = [eff_evaluator, sf_evaluator, wp_map, wp_names, - variation, + variation, sample_type, logger_name](const ROOT::RVec &etas, const ROOT::RVec &pts, const ROOT::RVec &btag_value, @@ -1996,7 +1997,8 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, {variation, wp, flavors.at(i), std::abs(etas.at(i)), pts.at(i)}); jet_eff[wp] = eff_evaluator->evaluate( - {wp, flavors.at(i), std::abs(etas.at(i)), pts.at(i)}); + {sample_type, wp, flavors.at(i), std::abs(etas.at(i)), + pts.at(i)}); } else { jet_sf[wp] = 1.0; jet_eff[wp] = 1.0; From dac5312a47cf2f2bac696050be0184f8a1dac105 Mon Sep 17 00:00:00 2001 From: moritzmolch Date: Wed, 6 May 2026 09:40:45 +0200 Subject: [PATCH 08/20] Update BtaggingWP function to recommendations from BTV group --- include/jets.hxx | 12 ++-- src/jets.cxx | 184 ++++++++++++++++++++++++++++++----------------- 2 files changed, 125 insertions(+), 71 deletions(-) diff --git a/include/jets.hxx b/include/jets.hxx index 99d3ca85..9b682cc9 100644 --- a/include/jets.hxx +++ b/include/jets.hxx @@ -136,11 +136,13 @@ ROOT::RDF::RNode BtaggingWP(ROOT::RDF::RNode df, correctionManager::CorrectionManager &correction_manager, const std::string &outputname, const std::string &pt, - const std::string &eta, const std::string &flavor, - const std::string &jet_mask, const std::string &bjet_mask, - const std::string &jet_veto_mask, const std::string &sf_file, - const std::string &sf_name, const std::string &variation, - const std::string &btag_wp); + const std::string &eta, const std::string &btag_value, + const std::string &flavor, const std::string &jet_mask, + const std::string &bjet_mask, const std::string &jet_veto_mask, + const std::string &sf_file, const std::string &sf_name, + const std::string &sf_wp_name, const std::string &eff_file, + const std::string &eff_name, const std::string &sample_type, + const std::string &variation, const std::string &btag_wp_name); ROOT::RDF::RNode BtaggingMultipleWP(ROOT::RDF::RNode df, correctionManager::CorrectionManager &correction_manager, diff --git a/src/jets.cxx b/src/jets.cxx index 4164c3db..3ce1b72c 100644 --- a/src/jets.cxx +++ b/src/jets.cxx @@ -1742,38 +1742,42 @@ BtaggingShape(ROOT::RDF::RNode df, } /** - * @brief This function calculates the b-tagging scale factor. The scale - * factor corrects inconsistencies in the b-tagging efficiency between data and + * @brief This function calculates the event b jet tagging scale factor for + * a setup with a single working point. The scale factor corrects + * inconsistencies in the b-tagging efficiency between data and * simulation. The scale factors are loaded from a correctionlib file - * using a specified scale factor name and variation. - * - * This producer can be used to evaluate working point based scale factors. It - * is defined based on scale factors provided by BTV POG for Run3 2024. + * using a specified scale factor name and variation. In addition, tagging + * efficiencies of the jets are loaded from a separate correctionlib file to be + * able to apply the appropriate scale factor. * - * More information from BTV POG can be found here - * https://btv-wiki.docs.cern.ch/ScaleFactors/ + * The procedure follows the recommendations of the BTV group: + * https://btv-wiki.docs.cern.ch/PerformanceCalibration/fixedWPSFRecommendations/#scale-factor-recommendations-for-event-reweighting * * @param df input dataframe * @param correction_manager correction manager responsible for loading the - * correction file + * correction file * @param outputname name of the output column containing the b-tagging scale - * factor + * factor * @param pt name of the column containing the transverse momenta of jets * @param eta name of the column containing the pseudorapidity of jets + * @param btag_value name of the column containing the btag scores of jets * @param flavor name of the column containing the flavors of jets, usually used - * flavors are: 5=b-jet, 4=c-jet, 0=light jet (g, u, d, s) + * flavors are: 5=b-jet, 4=c-jet, 0=light jet (g, u, d, s) * @param jet_mask name of the column containing the mask for good/selected jets * @param bjet_mask name of the column containing the mask for good/selected - * b-jets + * b-jets * @param jet_veto_mask name of the column containing the veto mask for - * overlapping jets (e.g. with selected lepton pairs) + * overlapping jets (e.g. with selected lepton pairs) * @param sf_file path to the file with the b-tagging scale factors - * @param sf_name name of the b-tagging scale factor correction e.g. - * "deepJet_shape" + * @param sf_name name of the b-tagging scale factor correction + * @param sf_wp_name name of the correction set containing the b tagging score + * cuts for the different working points + * @param eff_file path to the file with the b jet tagging efficiencies + * @param eff_name name of the b jet tagging efficiency correction set * @param variation name the scale factor variation, available values: - * central, down_*, up_* (* name of specific variation) - * @param btag_wp string that specifies the b-tagging working point used in an - * analysis e.g. "L", "M", "T", ... + * central, down_*, up_* (* name of specific variation) + * @param btag_wp_name string that specifies the b-tagging working point used in + an analysis e.g. "L", "M", "T", ... * * @return a new dataframe containing the new column */ @@ -1781,67 +1785,115 @@ ROOT::RDF::RNode BtaggingWP(ROOT::RDF::RNode df, correctionManager::CorrectionManager &correction_manager, const std::string &outputname, const std::string &pt, - const std::string &eta, const std::string &flavor, - const std::string &jet_mask, const std::string &bjet_mask, - const std::string &jet_veto_mask, const std::string &sf_file, - const std::string &sf_name, const std::string &variation, - const std::string &btag_wp) { - Logger::get("physicsobject::jet::scalefactor::BtaggingWP") - ->debug( - "Setting up functions for wp based b-tag sf with correctionlib"); - Logger::get("physicsobject::jet::scalefactor::BtaggingWP") - ->debug("Correction algorithm - Name {}", sf_name); - auto evaluator = correction_manager.loadCorrection(sf_file, sf_name); + const std::string &eta, const std::string &btag_value, + const std::string &flavor, const std::string &jet_mask, + const std::string &bjet_mask, const std::string &jet_veto_mask, + const std::string &sf_file, const std::string &sf_name, + const std::string &sf_wp_name, const std::string &eff_file, + const std::string &eff_name, const std::string &sample_type, + const std::string &variation, const std::string &btag_wp_name) { + // Set the logger name for better readability in debug messages + const std::string logger_name = + "physicsobject::jet::scalefactor::BtaggingWP"; + + // Debug messages for loading corrections + Logger::get(logger_name) + ->debug("Setting up functions for single WP setup with correctionlib"); + Logger::get(logger_name)->debug("correction cset name {}", sf_name); + Logger::get(logger_name)->debug("working point cset name {}", sf_wp_name); + Logger::get(logger_name)->debug("efficiency cset name {}", eff_name); + + // Get evaluators for SF, WP definitions, and from correctionlib files + auto sf_evaluator = correction_manager.loadCorrection(sf_file, sf_name); + auto wp_evaluator = correction_manager.loadCorrection(sf_file, sf_wp_name); + auto eff_evaluator = correction_manager.loadCorrection(eff_file, eff_name); + + // Define a map between the b-tagging working point name and the + // corresponding discriminator cut value. A custom value 'N' is used in the + // case the jet does not pass the loosest working point. Note that the list + // of working point names must be ordered from the tightest to the loosest + // one. + float btag_wp_cut = wp_evaluator->evaluate({btag_wp}); // In nanoAODv12 the type of jet flavor was changed to UChar_t // For v9 compatibility a type casting is applied - auto [df1, flavor_column] = + auto [df1, flavor_column_v12] = utility::Cast, ROOT::RVec>( df, flavor + "_v12", "ROOT::VecOps::RVec", flavor); - auto btagSF_lambda = [evaluator, variation, - btag_wp](const ROOT::RVec &etas, - const ROOT::RVec &pts, - const ROOT::RVec &flavors_v12, - const ROOT::RVec &jet_mask, - const ROOT::RVec &bjet_mask, - const ROOT::RVec &jet_veto_mask) { + auto b_tagging_sf = [eff_evaluator, sf_evaluator, btag_wp_cut, btag_wp_name, + variation, sample_type, + logger_name](const ROOT::RVec &etas, + const ROOT::RVec &pts, + const ROOT::RVec &btag_value, + const ROOT::RVec &flavors_v12, + const ROOT::RVec &jet_mask, + const ROOT::RVec &bjet_mask, + const ROOT::RVec &jet_veto_mask) { + Logger::get(logger_name) + ->debug("calculate b jet tagging event weight in single WP setup"); + Logger::get(logger_name)->debug("variation name {}", variation); + + // Define the event scale factor + float sf = 1.0; + + // Cast flavor column to integers auto flavors = static_cast>(flavors_v12); - Logger::get("physicsobject::jet::scalefactor::BtaggingWP") - ->debug("Variation - Name {}", variation); - float sf = 1.; + for (int i = 0; i < pts.size(); i++) { - Logger::get("physicsobject::jet::scalefactor::BtaggingWP") - ->debug("jet masks - jet {}, b-jet {}, jet veto {}", - jet_mask.at(i), bjet_mask.at(i), jet_veto_mask.at(i)); - // considering only good jets/b-jets, this is needed since jets and - // bjets might have different quality cuts depending on the analysis - if ((jet_mask.at(i) || bjet_mask.at(i)) && jet_veto_mask.at(i)) { - Logger::get("physicsobject::jet::scalefactor::BtaggingWP") - ->debug("SF - pt {}, eta {}, btag wp {}, flavor {}", - pts.at(i), etas.at(i), btag_wp, flavors.at(i)); - float jet_sf = 1.; - // considering only phase space where the scale factors are - // defined - if (pts.at(i) >= 20.0 && pts.at(i) < 10000.0 && - std::abs(etas.at(i)) < 2.5) { - jet_sf = - evaluator->evaluate({variation, btag_wp, flavors.at(i), + Logger::get(logger_name) + ->debug("SF - pt {}, eta {}, b tagging score {}, flavor {}", + pts.at(i), etas.at(i), btag_value.at(i), flavors.at(i)); + + // Skip jets that do not pass the jet/b jet selection + if (!((jet_mask.at(i) || bjet_mask.at(i)) && jet_veto_mask.at(i))) { + continue; + } + + // Obtain scale factors in the phase space where they are + // well-defined + float jet_eff = 1.0; + float jet_sf = 1.0; + if (pts.at(i) >= 20.0 && pts.at(i) < 10000.0 && + std::abs(etas.at(i)) < 2.5 && btag_value.at(i) >= 0) { + jet_sf = + sf_evaluator->evaluate({variation, btag_wp_name, flavors.at(i), + std::abs(etas.at(i)), pts.at(i)}); + jet_eff = + eff_evaluator->evaluate({sample_type, btag_wp_name, flavors.at(i), std::abs(etas.at(i)), pts.at(i)}); - } - Logger::get("physicsobject::jet::scalefactor::BtaggingWP") - ->debug("Jet Scale Factor {}", jet_sf); - sf *= jet_sf; } + Logger::get(logger_name)->debug("got SF {}", jet_sf); + Logger::get(logger_name)->debug("got efficiency {}", jet_eff); + + // Multiply this jet's contribution to the event scale factor based + // on the working point it passes. + float jet_comp = 1.0; + if (btag_value.at(i) >= btag_wp_cut) { + jet_comp = jet_sf; + } else { + jet_comp = (1.0 - jet_sf * jet_eff) / (1.0 - jet_eff); + } + + // Debug message for this jet's contribution to the event scale + // factor + Logger::get(logger_name) + ->debug("Jet contribution to event b tagging SF {}", jet_comp); + + // Multiply the jet's contribution to the event scale factor + sf *= jet_comp; }; - Logger::get("physicsobject::jet::scalefactor::BtaggingWP") - ->debug("Event Scale Factor {}", sf); + + // Debug message for event scale factor after all jets have been + // processed + Logger::get(logger_name)->debug("event scale factor: {}", sf); + return sf; }; - auto df2 = df1.Define( - outputname, btagSF_lambda, - {pt, eta, flavor_column, jet_mask, bjet_mask, jet_veto_mask}); - return df2; + + return df1.Define(outputname, b_tagging_sf, + {pt, eta, btag_value, flavor_column_v12, jet_mask, + bjet_mask, jet_veto_mask}); } /** From c048c8c3edcdff51fe7afa27ae8fc17545523968 Mon Sep 17 00:00:00 2001 From: moritzmolch Date: Wed, 6 May 2026 09:44:49 +0200 Subject: [PATCH 09/20] Fix typo --- src/jets.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jets.cxx b/src/jets.cxx index 3ce1b72c..a5c933da 100644 --- a/src/jets.cxx +++ b/src/jets.cxx @@ -1813,7 +1813,7 @@ BtaggingWP(ROOT::RDF::RNode df, // case the jet does not pass the loosest working point. Note that the list // of working point names must be ordered from the tightest to the loosest // one. - float btag_wp_cut = wp_evaluator->evaluate({btag_wp}); + float btag_wp_cut = wp_evaluator->evaluate({btag_wp_name}); // In nanoAODv12 the type of jet flavor was changed to UChar_t // For v9 compatibility a type casting is applied From dcb52e4e508e2b629e5cda71e91056c494884e74 Mon Sep 17 00:00:00 2001 From: moritzmolch Date: Wed, 6 May 2026 09:50:13 +0200 Subject: [PATCH 10/20] Run clang-format --- src/jets.cxx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/jets.cxx b/src/jets.cxx index a5c933da..8e8ac7b7 100644 --- a/src/jets.cxx +++ b/src/jets.cxx @@ -1856,12 +1856,12 @@ BtaggingWP(ROOT::RDF::RNode df, float jet_sf = 1.0; if (pts.at(i) >= 20.0 && pts.at(i) < 10000.0 && std::abs(etas.at(i)) < 2.5 && btag_value.at(i) >= 0) { - jet_sf = - sf_evaluator->evaluate({variation, btag_wp_name, flavors.at(i), - std::abs(etas.at(i)), pts.at(i)}); - jet_eff = - eff_evaluator->evaluate({sample_type, btag_wp_name, flavors.at(i), - std::abs(etas.at(i)), pts.at(i)}); + jet_sf = sf_evaluator->evaluate( + {variation, btag_wp_name, flavors.at(i), + std::abs(etas.at(i)), pts.at(i)}); + jet_eff = eff_evaluator->evaluate( + {sample_type, btag_wp_name, flavors.at(i), + std::abs(etas.at(i)), pts.at(i)}); } Logger::get(logger_name)->debug("got SF {}", jet_sf); Logger::get(logger_name)->debug("got efficiency {}", jet_eff); From cd1f042fbdc7d60ea2a5fa1b7077ab4e3de26e76 Mon Sep 17 00:00:00 2001 From: moritzmolch Date: Wed, 6 May 2026 15:39:53 +0200 Subject: [PATCH 11/20] Add light-flavor jet scale factors Co-authored-by: Copilot --- include/jets.hxx | 9 ++++---- src/jets.cxx | 54 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/include/jets.hxx b/include/jets.hxx index 9b682cc9..8bfd830e 100644 --- a/include/jets.hxx +++ b/include/jets.hxx @@ -139,10 +139,11 @@ BtaggingWP(ROOT::RDF::RNode df, const std::string &eta, const std::string &btag_value, const std::string &flavor, const std::string &jet_mask, const std::string &bjet_mask, const std::string &jet_veto_mask, - const std::string &sf_file, const std::string &sf_name, - const std::string &sf_wp_name, const std::string &eff_file, - const std::string &eff_name, const std::string &sample_type, - const std::string &variation, const std::string &btag_wp_name); + const std::string &sf_file, const std::string &sf_bc_name, + const std::string &sf_lf_name, const std::string &sf_wp_name, + const std::string &eff_file, const std::string &eff_name, + const std::string &sample_type, const std::string &variation, + const std::string &btag_wp_name); ROOT::RDF::RNode BtaggingMultipleWP(ROOT::RDF::RNode df, correctionManager::CorrectionManager &correction_manager, diff --git a/src/jets.cxx b/src/jets.cxx index 8e8ac7b7..6b021d19 100644 --- a/src/jets.cxx +++ b/src/jets.cxx @@ -1788,10 +1788,11 @@ BtaggingWP(ROOT::RDF::RNode df, const std::string &eta, const std::string &btag_value, const std::string &flavor, const std::string &jet_mask, const std::string &bjet_mask, const std::string &jet_veto_mask, - const std::string &sf_file, const std::string &sf_name, - const std::string &sf_wp_name, const std::string &eff_file, - const std::string &eff_name, const std::string &sample_type, - const std::string &variation, const std::string &btag_wp_name) { + const std::string &sf_file, const std::string &sf_bc_name, + const std::string &sf_lf_name, const std::string &sf_wp_name, + const std::string &eff_file, const std::string &eff_name, + const std::string &sample_type, const std::string &variation, + const std::string &btag_wp_name) { // Set the logger name for better readability in debug messages const std::string logger_name = "physicsobject::jet::scalefactor::BtaggingWP"; @@ -1799,12 +1800,18 @@ BtaggingWP(ROOT::RDF::RNode df, // Debug messages for loading corrections Logger::get(logger_name) ->debug("Setting up functions for single WP setup with correctionlib"); - Logger::get(logger_name)->debug("correction cset name {}", sf_name); + Logger::get(logger_name) + ->debug("b/c jet correction cset name {}", sf_bc_name); + Logger::get(logger_name) + ->debug("light-flavor jet correction cset name {}", sf_lf_name); Logger::get(logger_name)->debug("working point cset name {}", sf_wp_name); Logger::get(logger_name)->debug("efficiency cset name {}", eff_name); // Get evaluators for SF, WP definitions, and from correctionlib files - auto sf_evaluator = correction_manager.loadCorrection(sf_file, sf_name); + auto sf_bc_evaluator = + correction_manager.loadCorrection(sf_file, sf_bc_name); + auto sf_lf_evaluator = + correction_manager.loadCorrection(sf_file, sf_lf_name); auto wp_evaluator = correction_manager.loadCorrection(sf_file, sf_wp_name); auto eff_evaluator = correction_manager.loadCorrection(eff_file, eff_name); @@ -1821,10 +1828,10 @@ BtaggingWP(ROOT::RDF::RNode df, utility::Cast, ROOT::RVec>( df, flavor + "_v12", "ROOT::VecOps::RVec", flavor); - auto b_tagging_sf = [eff_evaluator, sf_evaluator, btag_wp_cut, btag_wp_name, - variation, sample_type, - logger_name](const ROOT::RVec &etas, - const ROOT::RVec &pts, + auto b_tagging_sf = [eff_evaluator, sf_bc_evaluator, sf_lf_evaluator, + btag_wp_cut, btag_wp_name, variation, sample_type, + logger_name](const ROOT::RVec &pts, + const ROOT::RVec &etas, const ROOT::RVec &btag_value, const ROOT::RVec &flavors_v12, const ROOT::RVec &jet_mask, @@ -1842,7 +1849,8 @@ BtaggingWP(ROOT::RDF::RNode df, for (int i = 0; i < pts.size(); i++) { Logger::get(logger_name) - ->debug("SF - pt {}, eta {}, b tagging score {}, flavor {}", + ->debug("Run on jet with pt {}, eta {}, b tagging score {}, " + "flavor {}", pts.at(i), etas.at(i), btag_value.at(i), flavors.at(i)); // Skip jets that do not pass the jet/b jet selection @@ -1850,6 +1858,23 @@ BtaggingWP(ROOT::RDF::RNode df, continue; } + // Switch to the correct evaluator for light-flavor or for b/c jets + const correction::Correction *sf_evaluator; + if (flavors.at(i) == 5 || flavors.at(i) == 4) { + Logger::get(logger_name) + ->debug("using b/c jet scale factor evaluator"); + sf_evaluator = sf_bc_evaluator; + } else if (flavors.at(i) == 0) { + Logger::get(logger_name) + ->debug("using light-flavor jet scale factor evaluator"); + sf_evaluator = sf_lf_evaluator; + } else { + Logger::get(logger_name) + ->error("jet flavor {} not recognized, skipping this jet", + flavors.at(i)); + throw std::runtime_error("jet flavor not recognized"); + } + // Obtain scale factors in the phase space where they are // well-defined float jet_eff = 1.0; @@ -1983,8 +2008,8 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, auto b_tagging_sf = [eff_evaluator, sf_evaluator, wp_map, wp_names, variation, sample_type, - logger_name](const ROOT::RVec &etas, - const ROOT::RVec &pts, + logger_name](const ROOT::RVec &pts, + const ROOT::RVec &etas, const ROOT::RVec &btag_value, const ROOT::RVec &flavors_v12, const ROOT::RVec &jet_mask, @@ -2003,7 +2028,8 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, for (int i = 0; i < pts.size(); i++) { Logger::get(logger_name) - ->debug("SF - pt {}, eta {}, b tagging score {}, flavor {}", + ->debug("Run on jet with pt {}, eta {}, b tagging score {}, " + "flavor {}", pts.at(i), etas.at(i), btag_value.at(i), flavors.at(i)); // Skip jets that do not pass the jet/b jet selection From 2fc7d8e06cdc824aabe4e9bd838363b522f83d5f Mon Sep 17 00:00:00 2001 From: moritzmolch Date: Wed, 6 May 2026 18:27:28 +0200 Subject: [PATCH 12/20] Update multiple WP function to also use mistagging SFs --- include/jets.hxx | 6 +++--- src/jets.cxx | 37 ++++++++++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/include/jets.hxx b/include/jets.hxx index 8bfd830e..dd0ae17e 100644 --- a/include/jets.hxx +++ b/include/jets.hxx @@ -152,9 +152,9 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, const std::string &flavor, const std::string &jet_mask, const std::string &bjet_mask, const std::string &jet_veto_mask, const std::string &sf_file, - const std::string &sf_name, const std::string &sf_wp_name, - const std::string &eff_file, const std::string &eff_name, - const std::string &sample_type, + const std::string &sf_bc_name, const std::string &sf_lf_name, + const std::string &sf_wp_name, const std::string &eff_file, + const std::string &eff_name, const std::string &sample_type, const std::string &variation); } // end namespace scalefactor } // end namespace jet diff --git a/src/jets.cxx b/src/jets.cxx index 6b021d19..6e85c971 100644 --- a/src/jets.cxx +++ b/src/jets.cxx @@ -1967,9 +1967,9 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, const std::string &flavor, const std::string &jet_mask, const std::string &bjet_mask, const std::string &jet_veto_mask, const std::string &sf_file, - const std::string &sf_name, const std::string &sf_wp_name, - const std::string &eff_file, const std::string &eff_name, - const std::string &sample_type, + const std::string &sf_bc_name, const std::string &sf_lf_name, + const std::string &sf_wp_name, const std::string &eff_file, + const std::string &eff_name, const std::string &sample_type, const std::string &variation) { // Set the logger name for better readability in debug messages const std::string logger_name = @@ -1979,12 +1979,18 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, Logger::get(logger_name) ->debug( "Setting up functions for multiple WP setup with correctionlib"); - Logger::get(logger_name)->debug("correction cset name {}", sf_name); + Logger::get(logger_name) + ->debug("b/c jet correction cset name {}", sf_bc_name); + Logger::get(logger_name) + ->debug("light flavor jet correction cset name {}", sf_lf_name); Logger::get(logger_name)->debug("working point cset name {}", sf_wp_name); Logger::get(logger_name)->debug("efficiency cset name {}", eff_name); // Get evaluators for SF, WP definitions, and from correctionlib files - auto sf_evaluator = correction_manager.loadCorrection(sf_file, sf_name); + auto sf_bc_evaluator = + correction_manager.loadCorrection(sf_file, sf_bc_name); + auto sf_lf_evaluator = + correction_manager.loadCorrection(sf_file, sf_lf_name); auto wp_evaluator = correction_manager.loadCorrection(sf_file, sf_wp_name); auto eff_evaluator = correction_manager.loadCorrection(eff_file, eff_name); @@ -2006,8 +2012,8 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, utility::Cast, ROOT::RVec>( df, flavor + "_v12", "ROOT::VecOps::RVec", flavor); - auto b_tagging_sf = [eff_evaluator, sf_evaluator, wp_map, wp_names, - variation, sample_type, + auto b_tagging_sf = [eff_evaluator, sf_bc_evaluator, sf_lf_evaluator, + wp_map, wp_names, variation, sample_type, logger_name](const ROOT::RVec &pts, const ROOT::RVec &etas, const ROOT::RVec &btag_value, @@ -2037,6 +2043,23 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, continue; } + // Switch to the correct evaluator for light-flavor or for b/c jets + const correction::Correction *sf_evaluator; + if (flavors.at(i) == 5 || flavors.at(i) == 4) { + Logger::get(logger_name) + ->debug("using b/c jet scale factor evaluator"); + sf_evaluator = sf_bc_evaluator; + } else if (flavors.at(i) == 0) { + Logger::get(logger_name) + ->debug("using light-flavor jet scale factor evaluator"); + sf_evaluator = sf_lf_evaluator; + } else { + Logger::get(logger_name) + ->error("jet flavor {} not recognized, skipping this jet", + flavors.at(i)); + throw std::runtime_error("jet flavor not recognized"); + } + // Get the passed b jet tagging working point for this jet // A custom value 'N' is used in the case the jet does not pass // the loosest working point for the given b jet tagging algorithm. From 2a403333c8fbaef9fc20cff563acff963e06bea3 Mon Sep 17 00:00:00 2001 From: moritzmolch Date: Thu, 7 May 2026 16:08:23 +0200 Subject: [PATCH 13/20] Fix -nan output for SFs for jets out of acceptance --- src/jets.cxx | 102 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 77 insertions(+), 25 deletions(-) diff --git a/src/jets.cxx b/src/jets.cxx index 6e85c971..2dfa3467 100644 --- a/src/jets.cxx +++ b/src/jets.cxx @@ -1854,7 +1854,29 @@ BtaggingWP(ROOT::RDF::RNode df, pts.at(i), etas.at(i), btag_value.at(i), flavors.at(i)); // Skip jets that do not pass the jet/b jet selection - if (!((jet_mask.at(i) || bjet_mask.at(i)) && jet_veto_mask.at(i))) { + if ( + !( + (jet_mask.at(i) || bjet_mask.at(i)) + && jet_veto_mask.at(i) + ) + ) { + Logger::get(logger_name) + ->debug("Skip jet that does not pass selection criteria"); + continue; + } + + // Skip jets out of the acceptance of the phase space in which the + // corrections have been calculated + if ( + !( + pts.at(i) >= 20.0 + && pts.at(i) < 10000.0 + && std::abs(etas.at(i)) < 2.5 + && btag_value.at(i) >= 0 + ) + ) { + Logger::get(logger_name) + ->debug("Skip jet that is out of validity of corrections"); continue; } @@ -1877,17 +1899,21 @@ BtaggingWP(ROOT::RDF::RNode df, // Obtain scale factors in the phase space where they are // well-defined - float jet_eff = 1.0; - float jet_sf = 1.0; - if (pts.at(i) >= 20.0 && pts.at(i) < 10000.0 && - std::abs(etas.at(i)) < 2.5 && btag_value.at(i) >= 0) { - jet_sf = sf_evaluator->evaluate( - {variation, btag_wp_name, flavors.at(i), - std::abs(etas.at(i)), pts.at(i)}); - jet_eff = eff_evaluator->evaluate( - {sample_type, btag_wp_name, flavors.at(i), - std::abs(etas.at(i)), pts.at(i)}); + float jet_sf = sf_evaluator->evaluate( + {variation, btag_wp_name, flavors.at(i), + std::abs(etas.at(i)), pts.at(i)}); + float jet_eff = eff_evaluator->evaluate( + {sample_type, btag_wp_name, flavors.at(i), + std::abs(etas.at(i)), pts.at(i)}); + + // Clip efficiency to avoid division by 0 + if (jet_eff >= 0.999) { + jet_eff = 0.999; + } else if (jet_eff <= 0.001) { + jet_eff = 0.001; } + + // Log values of the scale factor and efficiency per jet Logger::get(logger_name)->debug("got SF {}", jet_sf); Logger::get(logger_name)->debug("got efficiency {}", jet_eff); @@ -2039,7 +2065,29 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, pts.at(i), etas.at(i), btag_value.at(i), flavors.at(i)); // Skip jets that do not pass the jet/b jet selection - if (!((jet_mask.at(i) || bjet_mask.at(i)) && jet_veto_mask.at(i))) { + if ( + !( + (jet_mask.at(i) || bjet_mask.at(i)) + && jet_veto_mask.at(i) + ) + ) { + Logger::get(logger_name) + ->debug("Skip jet that does not pass selection criteria"); + continue; + } + + // Skip jets out of the acceptance of the phase space in which the + // corrections have been calculated + if ( + !( + pts.at(i) >= 20.0 + && pts.at(i) < 10000.0 + && std::abs(etas.at(i)) < 2.5 + && btag_value.at(i) >= 0 + ) + ) { + Logger::get(logger_name) + ->debug("Skip jet that is out of validity of corrections"); continue; } @@ -2092,22 +2140,26 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, std::map jet_eff; std::map jet_sf; for (const auto &wp : wps) { - if (pts.at(i) >= 20.0 && pts.at(i) < 10000.0 && - std::abs(etas.at(i)) < 2.5 && btag_value.at(i) >= 0) { - jet_sf[wp] = sf_evaluator->evaluate( - {variation, wp, flavors.at(i), std::abs(etas.at(i)), - pts.at(i)}); - jet_eff[wp] = eff_evaluator->evaluate( - {sample_type, wp, flavors.at(i), std::abs(etas.at(i)), - pts.at(i)}); - } else { - jet_sf[wp] = 1.0; - jet_eff[wp] = 1.0; + // Get the scale factors from the correction sets + jet_sf[wp] = sf_evaluator->evaluate( + {variation, wp, flavors.at(i), std::abs(etas.at(i)), + pts.at(i)}); + jet_eff[wp] = eff_evaluator->evaluate( + {sample_type, wp, flavors.at(i), std::abs(etas.at(i)), + pts.at(i)}); + + // Clip efficiency to avoid division by 0 + if (jet_eff[wp] >= 0.999) { + jet_eff[wp] = 0.999; + } else if (jet_eff[wp] <= 0.001) { + jet_eff[wp] = 0.001; } + + // Log the values of scale factor and efficiency for this jet Logger::get(logger_name) - ->debug("got SFs {} (WP {})", jet_sf[wp], wp); + ->debug("got SF {} (WP {})", jet_sf[wp], wp); Logger::get(logger_name) - ->debug("got efficiencies {} (WP {})", jet_eff[wp], wp); + ->debug("got efficiency {} (WP {})", jet_eff[wp], wp); } // Multiply this jet's contribution to the event scale factor based From 1931ddbbb8fb20e576b64db2d070cb9576ce1225 Mon Sep 17 00:00:00 2001 From: moritzmolch Date: Thu, 7 May 2026 16:10:53 +0200 Subject: [PATCH 14/20] Run clang-format --- src/jets.cxx | 56 ++++++++++++++++------------------------------------ 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/src/jets.cxx b/src/jets.cxx index 2dfa3467..bd677e9f 100644 --- a/src/jets.cxx +++ b/src/jets.cxx @@ -1854,12 +1854,7 @@ BtaggingWP(ROOT::RDF::RNode df, pts.at(i), etas.at(i), btag_value.at(i), flavors.at(i)); // Skip jets that do not pass the jet/b jet selection - if ( - !( - (jet_mask.at(i) || bjet_mask.at(i)) - && jet_veto_mask.at(i) - ) - ) { + if (!((jet_mask.at(i) || bjet_mask.at(i)) && jet_veto_mask.at(i))) { Logger::get(logger_name) ->debug("Skip jet that does not pass selection criteria"); continue; @@ -1867,14 +1862,8 @@ BtaggingWP(ROOT::RDF::RNode df, // Skip jets out of the acceptance of the phase space in which the // corrections have been calculated - if ( - !( - pts.at(i) >= 20.0 - && pts.at(i) < 10000.0 - && std::abs(etas.at(i)) < 2.5 - && btag_value.at(i) >= 0 - ) - ) { + if (!(pts.at(i) >= 20.0 && pts.at(i) < 10000.0 && + std::abs(etas.at(i)) < 2.5 && btag_value.at(i) >= 0)) { Logger::get(logger_name) ->debug("Skip jet that is out of validity of corrections"); continue; @@ -1899,12 +1888,12 @@ BtaggingWP(ROOT::RDF::RNode df, // Obtain scale factors in the phase space where they are // well-defined - float jet_sf = sf_evaluator->evaluate( - {variation, btag_wp_name, flavors.at(i), - std::abs(etas.at(i)), pts.at(i)}); + float jet_sf = + sf_evaluator->evaluate({variation, btag_wp_name, flavors.at(i), + std::abs(etas.at(i)), pts.at(i)}); float jet_eff = eff_evaluator->evaluate( - {sample_type, btag_wp_name, flavors.at(i), - std::abs(etas.at(i)), pts.at(i)}); + {sample_type, btag_wp_name, flavors.at(i), std::abs(etas.at(i)), + pts.at(i)}); // Clip efficiency to avoid division by 0 if (jet_eff >= 0.999) { @@ -2065,12 +2054,7 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, pts.at(i), etas.at(i), btag_value.at(i), flavors.at(i)); // Skip jets that do not pass the jet/b jet selection - if ( - !( - (jet_mask.at(i) || bjet_mask.at(i)) - && jet_veto_mask.at(i) - ) - ) { + if (!((jet_mask.at(i) || bjet_mask.at(i)) && jet_veto_mask.at(i))) { Logger::get(logger_name) ->debug("Skip jet that does not pass selection criteria"); continue; @@ -2078,14 +2062,8 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, // Skip jets out of the acceptance of the phase space in which the // corrections have been calculated - if ( - !( - pts.at(i) >= 20.0 - && pts.at(i) < 10000.0 - && std::abs(etas.at(i)) < 2.5 - && btag_value.at(i) >= 0 - ) - ) { + if (!(pts.at(i) >= 20.0 && pts.at(i) < 10000.0 && + std::abs(etas.at(i)) < 2.5 && btag_value.at(i) >= 0)) { Logger::get(logger_name) ->debug("Skip jet that is out of validity of corrections"); continue; @@ -2141,12 +2119,12 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, std::map jet_sf; for (const auto &wp : wps) { // Get the scale factors from the correction sets - jet_sf[wp] = sf_evaluator->evaluate( - {variation, wp, flavors.at(i), std::abs(etas.at(i)), - pts.at(i)}); - jet_eff[wp] = eff_evaluator->evaluate( - {sample_type, wp, flavors.at(i), std::abs(etas.at(i)), - pts.at(i)}); + jet_sf[wp] = + sf_evaluator->evaluate({variation, wp, flavors.at(i), + std::abs(etas.at(i)), pts.at(i)}); + jet_eff[wp] = + eff_evaluator->evaluate({sample_type, wp, flavors.at(i), + std::abs(etas.at(i)), pts.at(i)}); // Clip efficiency to avoid division by 0 if (jet_eff[wp] >= 0.999) { From 777c96e91f5aa1706875cab1ff3a9b7b6b48f080 Mon Sep 17 00:00:00 2001 From: moritzmolch Date: Fri, 8 May 2026 14:20:59 +0200 Subject: [PATCH 15/20] Extend multiple WP setup to XT and XXT and add warning if jet SF is +-inf or nan --- src/jets.cxx | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/jets.cxx b/src/jets.cxx index bd677e9f..33921309 100644 --- a/src/jets.cxx +++ b/src/jets.cxx @@ -2014,7 +2014,7 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, // case the jet does not pass the loosest working point. Note that the list // of working point names must be ordered from the tightest to the loosest // one. - std::vector wp_names = {"T", "M", "L"}; + std::vector wp_names = {"XXT", "XT", "T", "M", "L"}; std::map wp_map; for (const auto &wp : wp_names) { wp_map[wp] = wp_evaluator->evaluate({wp}); @@ -2101,10 +2101,15 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, btag_wp); // Define list of b tagging working point scale factors needed for - // this jet + // this jet; the list must be sorted from the looser to + // the tighter WPs. std::vector wps = {}; - if (btag_wp == "T") { - wps = {"T"}; + } else if (btag_wp == "XXT") { + wps = {"XXT"}; + } else if (btag_wp == "XT") { + wps = {"XT", "XXT"}; + } else if (btag_wp == "T") { + wps = {"T", "XT"}; } else if (btag_wp == "M") { wps = {"M", "T"}; } else if (btag_wp == "L") { @@ -2143,16 +2148,19 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, // Multiply this jet's contribution to the event scale factor based // on the working point it passes. float jet_comp = 1.0; - if (btag_wp == "T") { - jet_comp = jet_sf["T"]; - } else if (btag_wp == "M") { - jet_comp = - (jet_sf["M"] * jet_eff["M"] - jet_sf["T"] * jet_eff["T"]) / - (jet_eff["M"] - jet_eff["T"]); - } else if (btag_wp == "L") { + if (btag_wp == "XXT") { + jet_comp = jet_sf["XXT"]; + } else if ( + (btag_wp == "XT") + || (btag_wp == "T") + || (btag_wp == "M") + || (btag_wp == "L") + ) { + auto low_wp = wps[0]; + auto high_wp = wps[1]; jet_comp = - (jet_sf["L"] * jet_eff["L"] - jet_sf["M"] * jet_eff["M"]) / - (jet_eff["L"] - jet_eff["M"]); + (jet_sf[low_wp] * jet_eff[low_wp] - jet_sf[high_wp] * jet_eff[high_wp]) / + (jet_eff[low_wp] - jet_eff[high_wp]); } else if (btag_wp == "N") { jet_comp = (1.0 - jet_sf["L"] * jet_eff["L"]) / (1.0 - jet_eff["L"]); @@ -2172,6 +2180,14 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, jet_comp = 1.0; } + // Emit a warning if the SF is extremely negative, extremely + // positive, or nan. + if (std::isnan(jet_comp) || !std::isfinite(jet_comp)) { + Logger::get(logger_name) + ->warning("got invalid jet contribution {} to b tagging SF", + jet_comp); + } + // Debug message for this jet's contribution to the event scale // factor Logger::get(logger_name) From ea054aa5477738ebe7747420faf9ffeaa6288eab Mon Sep 17 00:00:00 2001 From: moritzmolch Date: Fri, 8 May 2026 15:23:44 +0200 Subject: [PATCH 16/20] Fix typo --- src/jets.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jets.cxx b/src/jets.cxx index 33921309..c2776344 100644 --- a/src/jets.cxx +++ b/src/jets.cxx @@ -2184,7 +2184,7 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, // positive, or nan. if (std::isnan(jet_comp) || !std::isfinite(jet_comp)) { Logger::get(logger_name) - ->warning("got invalid jet contribution {} to b tagging SF", + ->warn("got invalid jet contribution {} to b tagging SF", jet_comp); } From 2fa380f6cdc5a8de8ad4db9b8c04f13a850d05b2 Mon Sep 17 00:00:00 2001 From: moritzmolch Date: Fri, 8 May 2026 15:24:45 +0200 Subject: [PATCH 17/20] Remove clipping of jet efficiency and fix typo in if else query --- src/jets.cxx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/jets.cxx b/src/jets.cxx index c2776344..2cf94d3a 100644 --- a/src/jets.cxx +++ b/src/jets.cxx @@ -2104,7 +2104,7 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, // this jet; the list must be sorted from the looser to // the tighter WPs. std::vector wps = {}; - } else if (btag_wp == "XXT") { + if (btag_wp == "XXT") { wps = {"XXT"}; } else if (btag_wp == "XT") { wps = {"XT", "XXT"}; @@ -2131,13 +2131,6 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, eff_evaluator->evaluate({sample_type, wp, flavors.at(i), std::abs(etas.at(i)), pts.at(i)}); - // Clip efficiency to avoid division by 0 - if (jet_eff[wp] >= 0.999) { - jet_eff[wp] = 0.999; - } else if (jet_eff[wp] <= 0.001) { - jet_eff[wp] = 0.001; - } - // Log the values of scale factor and efficiency for this jet Logger::get(logger_name) ->debug("got SF {} (WP {})", jet_sf[wp], wp); From 5095d508f1cc2e988f9ba971bb7b1c5ac77c3fab Mon Sep 17 00:00:00 2001 From: moritzmolch Date: Fri, 8 May 2026 15:25:35 +0200 Subject: [PATCH 18/20] Format code --- src/jets.cxx | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/jets.cxx b/src/jets.cxx index 2cf94d3a..5cab61fb 100644 --- a/src/jets.cxx +++ b/src/jets.cxx @@ -2143,17 +2143,13 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, float jet_comp = 1.0; if (btag_wp == "XXT") { jet_comp = jet_sf["XXT"]; - } else if ( - (btag_wp == "XT") - || (btag_wp == "T") - || (btag_wp == "M") - || (btag_wp == "L") - ) { + } else if ((btag_wp == "XT") || (btag_wp == "T") || + (btag_wp == "M") || (btag_wp == "L")) { auto low_wp = wps[0]; auto high_wp = wps[1]; - jet_comp = - (jet_sf[low_wp] * jet_eff[low_wp] - jet_sf[high_wp] * jet_eff[high_wp]) / - (jet_eff[low_wp] - jet_eff[high_wp]); + jet_comp = (jet_sf[low_wp] * jet_eff[low_wp] - + jet_sf[high_wp] * jet_eff[high_wp]) / + (jet_eff[low_wp] - jet_eff[high_wp]); } else if (btag_wp == "N") { jet_comp = (1.0 - jet_sf["L"] * jet_eff["L"]) / (1.0 - jet_eff["L"]); @@ -2178,7 +2174,7 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, if (std::isnan(jet_comp) || !std::isfinite(jet_comp)) { Logger::get(logger_name) ->warn("got invalid jet contribution {} to b tagging SF", - jet_comp); + jet_comp); } // Debug message for this jet's contribution to the event scale From eb9d466dbd0f5bf83f76bfb96e0c2639e91cf2e6 Mon Sep 17 00:00:00 2001 From: moritzmolch Date: Fri, 8 May 2026 22:32:38 +0200 Subject: [PATCH 19/20] Set finite values for edge cases, in which denominator and/or numerator of b-tagging SF contribution are nonpositive --- src/jets.cxx | 96 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 26 deletions(-) diff --git a/src/jets.cxx b/src/jets.cxx index 5cab61fb..4eeb04ee 100644 --- a/src/jets.cxx +++ b/src/jets.cxx @@ -1886,33 +1886,58 @@ BtaggingWP(ROOT::RDF::RNode df, throw std::runtime_error("jet flavor not recognized"); } - // Obtain scale factors in the phase space where they are - // well-defined + // Get the scale factor and the efficiency float jet_sf = sf_evaluator->evaluate({variation, btag_wp_name, flavors.at(i), std::abs(etas.at(i)), pts.at(i)}); - float jet_eff = eff_evaluator->evaluate( - {sample_type, btag_wp_name, flavors.at(i), std::abs(etas.at(i)), - pts.at(i)}); - - // Clip efficiency to avoid division by 0 - if (jet_eff >= 0.999) { - jet_eff = 0.999; - } else if (jet_eff <= 0.001) { - jet_eff = 0.001; - } + float jet_eff = + eff_evaluator->evaluate({sample_type, btag_wp_name, flavors.at(i), + std::abs(etas.at(i)), pts.at(i)}); - // Log values of the scale factor and efficiency per jet + // Log the values of scale factor and efficiency for this jet Logger::get(logger_name)->debug("got SF {}", jet_sf); Logger::get(logger_name)->debug("got efficiency {}", jet_eff); // Multiply this jet's contribution to the event scale factor based // on the working point it passes. - float jet_comp = 1.0; + float num = 1.0; + float denom = 1.0; if (btag_value.at(i) >= btag_wp_cut) { - jet_comp = jet_sf; + // We only need to define the numerator here + num = jet_sf; } else { - jet_comp = (1.0 - jet_sf * jet_eff) / (1.0 - jet_eff); + // Define numerator and denominator + num = 1.0 - jet_sf * jet_eff; + denom = 1.0 - jet_eff; + } + + // Handle edge cases where the denominator is not positive: Set + // the efficiency difference to 1 (TODO is this well-justified?) + if (denom <= 0) { + Logger::get(logger_name) + ->debug("got negative denominator {}, set it to 1.0", + denom); + denom = 1.0; + } + + // Calculate the jet contribution + auto jet_comp = num / denom; + + // Handle edge cases where the whole SF is negative, set it to 1 + // unity + if (jet_comp <= 0.0) { + Logger::get(logger_name) + ->debug("got nonpositve jet contribution {}, set it to 1.0", + jet_comp); + jet_comp = 1.0; + } + + // Emit a warning if the SF is extremely negative, extremely + // positive, or nan. + if (std::isnan(jet_comp) || !std::isfinite(jet_comp)) { + Logger::get(logger_name) + ->warn("got invalid jet contribution {} to b tagging SF", + jet_comp); } // Debug message for this jet's contribution to the event scale @@ -2140,19 +2165,25 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, // Multiply this jet's contribution to the event scale factor based // on the working point it passes. - float jet_comp = 1.0; + float num = 1.0; + float denom = 1.0; if (btag_wp == "XXT") { - jet_comp = jet_sf["XXT"]; + // We only need to define the numerator here + num = jet_sf["XXT"]; } else if ((btag_wp == "XT") || (btag_wp == "T") || (btag_wp == "M") || (btag_wp == "L")) { + // Get the name of the lower and the upper edge of the WP + // bin auto low_wp = wps[0]; auto high_wp = wps[1]; - jet_comp = (jet_sf[low_wp] * jet_eff[low_wp] - - jet_sf[high_wp] * jet_eff[high_wp]) / - (jet_eff[low_wp] - jet_eff[high_wp]); + + // Define numerator and denominator + num = jet_sf[low_wp] * jet_eff[low_wp] - jet_sf[high_wp] * jet_eff[high_wp]; + denom = jet_eff[low_wp] - jet_eff[high_wp]; } else if (btag_wp == "N") { - jet_comp = - (1.0 - jet_sf["L"] * jet_eff["L"]) / (1.0 - jet_eff["L"]); + // Define numerator and denominator + num = 1.0 - jet_sf["L"] * jet_eff["L"]; + denom = 1.0 - jet_eff["L"]; } else { Logger::get(logger_name) ->error("Arrived at unexpected b tagging working point {}", @@ -2161,10 +2192,23 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, "Arrived at unexpected b tagging working point " + btag_wp); } - // Force the SF to a positive value, set it to unity otherwise - if (jet_comp < 0.0) { + // Handle edge cases where the denominator is not positive: Set + // the efficiency difference to 1 (TODO is this well-justified?) + if (denom <= 0) { + Logger::get(logger_name) + ->debug("got negative denominator {}, set it to 1.0", + denom); + denom = 1.0; + } + + // Calculate the jet contribution + auto jet_comp = num / denom; + + // Handle edge cases where the whole SF is negative, set it to 1 + // unity + if (jet_comp <= 0.0) { Logger::get(logger_name) - ->debug("got negative jet contribution {}, set it to 1.0", + ->debug("got nonpositve jet contribution {}, set it to 1.0", jet_comp); jet_comp = 1.0; } From fe267920dd25933796137f5e9cbcbac5796dfc8d Mon Sep 17 00:00:00 2001 From: moritzmolch Date: Tue, 12 May 2026 12:04:34 +0200 Subject: [PATCH 20/20] Format with clang-format --- src/jets.cxx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/jets.cxx b/src/jets.cxx index 4eeb04ee..15de12a3 100644 --- a/src/jets.cxx +++ b/src/jets.cxx @@ -1890,9 +1890,9 @@ BtaggingWP(ROOT::RDF::RNode df, float jet_sf = sf_evaluator->evaluate({variation, btag_wp_name, flavors.at(i), std::abs(etas.at(i)), pts.at(i)}); - float jet_eff = - eff_evaluator->evaluate({sample_type, btag_wp_name, flavors.at(i), - std::abs(etas.at(i)), pts.at(i)}); + float jet_eff = eff_evaluator->evaluate( + {sample_type, btag_wp_name, flavors.at(i), std::abs(etas.at(i)), + pts.at(i)}); // Log the values of scale factor and efficiency for this jet Logger::get(logger_name)->debug("got SF {}", jet_sf); @@ -2178,7 +2178,8 @@ BtaggingMultipleWP(ROOT::RDF::RNode df, auto high_wp = wps[1]; // Define numerator and denominator - num = jet_sf[low_wp] * jet_eff[low_wp] - jet_sf[high_wp] * jet_eff[high_wp]; + num = jet_sf[low_wp] * jet_eff[low_wp] - + jet_sf[high_wp] * jet_eff[high_wp]; denom = jet_eff[low_wp] - jet_eff[high_wp]; } else if (btag_wp == "N") { // Define numerator and denominator