ATLAS Offline Software
Loading...
Searching...
No Matches
PMGHFProductionFractionTool.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
3*/
4
5// $Id: PMGHFProductionFractionTool.cxx
6
7// Systematics include(s):
10
11// Local include(s):
13
15
16// C++
17#include <filesystem>
18#include <fstream>
19#include <sstream>
20
21namespace fs = std::filesystem;
22
23namespace PMGTools
24{
26 asg::AsgMetadataTool(name),
29 m_showerGeneratorMapFile(""),
30 m_showerGenerator(""),
31 m_charmFilename(""),
32 m_bottomFilename(""),
33 m_charmProdFractionWeights({}),
34 m_bottomProdFractionWeights({})
35{
36 // Calibration area path
37 declareProperty("CalibrationAreaPath", m_calibrationAreaPath = "xAODBTaggingEfficiency/13TeV/HFReweighting-2022-04-19_v1",
38 "Path to the calibration area");
39
40 // MC Shower generator map
41 declareProperty("ShowerGeneratorMapFile", m_showerGeneratorMapFile = "HFProductionFractionsMap.txt",
42 "Input file name for the MC shower generator map");
43
44 // MC Shower generator software
45 declareProperty("ShowerGenerator", m_showerGenerator = "",
46 "MC Shower generator software");
47
48 // The fiducial charm/bottom pT cut (in MeV)
49 declareProperty("FiducialPtCut", m_fiducialPtCut = 5000,
50 "The fiducial charm/bottom pT cut. The recommended value is 5000 (MeV).");
51
52 // The fiducial charm/bottom eta cut
53 declareProperty("FiducialEtaCut", m_fiducialEtaCut = 2.5,
54 "The fiducial charm/bottom eta cut. The recommended value is 2.5.");
55}
56
58 // Tell the user what's happening:
59 ATH_MSG_INFO("Initializing " << name() << "...");
60
61 // Check for correct settings
62 if (m_showerGenerator.empty())
63 {
64 ATH_MSG_WARNING("The property `ShowerGenerator' was not set!");
65 return StatusCode::FAILURE;
66 }
67
68 // read input files and calculate weights
69 // populates m_charmProdFractionWeights and m_bottomProdFractionWeights
70 if (m_showerGenerator != "000000") {
72
73 // setup the SystematicsCache object
75 [this](const CP::SystematicSet &sys, ParameterSet &prod_fracs) {
76 return setSystematicVariation(sys, prod_fracs);
77 }
78 );
79
80 // Register systematics with the registry
82 if (registry.registerSystematics(*this) != StatusCode::SUCCESS) {
83 ATH_MSG_ERROR("Unknown systematic list");
84 return StatusCode::FAILURE;
85 }
86
87 // print
89
90 } else {
91 ATH_MSG_WARNING("The property `ShowerGenerator' was set to 000000, which means that the tool will return dummy weights of 1.0");
92 }
93
94 // Return gracefully
95 return StatusCode::SUCCESS;
96}
97
98StatusCode PMGHFProductionFractionTool::readProductionFractionsFile(const std::string & filename, std::map<CP::SystematicVariation, std::map<unsigned int, float>> &weights) {
99 /* Example file structure:
100 # Charm production fraction from literature (Eur. Phys. J. C (2016) 76:397)
101 411 421 431 4000
102 NOSYS 0.2404 0.6086 0.0802 0.0623
103 PROD_FRAC_CHARM_EIG_1 0.2405 0.6087 0.0770 0.0650
104 PROD_FRAC_CHARM_EIG_2 0.2439 0.6108 0.0779 0.0593
105 PROD_FRAC_CHARM_EIG_3 0.2461 0.6013 0.0808 0.0632
106 */
107 std::ifstream infile(filename);
108 std::string line;
109 int pdg1 = -1, pdg2 = -1, pdg3 = -1, pdg4 = -1;
110 while (std::getline(infile, line)) {
111 std::istringstream iss(line);
112 if (line.rfind("#", 0) == 0 || line.empty()) {
113 continue;
114 }
115 if (pdg1 < 0) {
116 if (!(iss >> pdg1 >> pdg2 >> pdg3 >> pdg4)) {
117 ATH_MSG_ERROR("Invalid formatting: " << line);
118 return StatusCode::FAILURE;
119 }
120 } else {
121 std::string sys;
122 float f1, f2, f3, f4;
123 if (!(iss >> sys >> f1 >> f2 >> f3 >> f4)) {
124 ATH_MSG_ERROR("Invalid formatting: " << line);
125 return StatusCode::FAILURE;
126 }
127 if (sys == "NOSYS") {
128 sys = "";
129 }
130 weights.insert({CP::SystematicVariation(sys), {{pdg1, f1}, {pdg2, f2}, {pdg3, f3}, {pdg4, f4}}});
131 }
132 }
133
134 // Close file
135 infile.close();
136
137 // return
138 return StatusCode::SUCCESS;
139}
140
142 // Step 0: find available files
143 // ________________________________________________________________
144
145 // Charm production fractions data file
146 std::string mapFilename = PathResolverFindCalibFile((fs::path(m_calibrationAreaPath) / fs::path(m_showerGeneratorMapFile)).string());
147 if (mapFilename.empty()) {
148 ATH_MSG_ERROR("Input file not found " << m_showerGeneratorMapFile);
149 return StatusCode::FAILURE;
150 }
151 ATH_MSG_INFO("Found input file " << mapFilename);
152
153 // Find all available MC generators
154 std::ifstream infile(mapFilename);
155 std::string line;
156 while (std::getline(infile, line)) {
157 std::istringstream iss(line);
158 if (line.rfind("#", 0) == 0 || line.empty()) {
159 continue;
160 }
161
162 std::string id, name;
163 if (!(iss >> id >> name)) {
164 ATH_MSG_ERROR("Invalid formatting: " << line);
165 return StatusCode::FAILURE;
166 } if (id == "4") {
167 m_charmFilename = std::move(name);
168 } else if (id == "5") {
169 m_bottomFilename = std::move(name);
170 } else {
171 m_showerGeneratorMap[id] = std::move(name);
172 }
173 }
174
175 // Close file
176 infile.close();
177
178 // Check if the requested MC shower version is available
180 ATH_MSG_ERROR("MC shower generator " << m_showerGenerator << " not found!");
181 ATH_MSG_INFO("Available generators are:");
182 for (auto &gen : m_showerGeneratorMap) {
183 ATH_MSG_INFO(gen.first);
184 }
185 return StatusCode::FAILURE;
186 }
187
188 // Step 1: read HF production fractions from literature
189 // ________________________________________________________________
190
191 // Charm production fractions data file
192 std::string charmFilename = PathResolverFindCalibFile((fs::path(m_calibrationAreaPath) / fs::path(m_charmFilename)).string());
193 if (charmFilename.empty()) {
194 ATH_MSG_ERROR("Input file not found " << m_charmFilename);
195 return StatusCode::FAILURE;
196 }
197 ATH_MSG_INFO("Found input file " << charmFilename);
198
199 // Bottom production fractions data file
200 std::string bottomFilename = PathResolverFindCalibFile((fs::path(m_calibrationAreaPath) / fs::path(m_bottomFilename)).string());
201 if (bottomFilename.empty()) {
202 ATH_MSG_ERROR("Input file not found " << m_bottomFilename);
203 return StatusCode::FAILURE;
204 }
205 ATH_MSG_INFO("Found input file " << bottomFilename);
206
207 // Configure the production fractions from files
210
211 // Step 2: read HF production fractions for a given MC shower
212 // ________________________________________________________________
213
214 // Read the config files from cvmfs
215 std::string filename = PathResolverFindCalibFile((fs::path(m_calibrationAreaPath) / fs::path(m_showerGeneratorMap[m_showerGenerator])).string());
216 if (filename.empty()) {
217 ATH_MSG_ERROR("Input file not found " << m_showerGeneratorMap[m_showerGenerator]);
218 return StatusCode::FAILURE;
219 }
220 ATH_MSG_INFO("Found input file " << filename);
221
222 // Configure the weights from the file
223 /* Example file structure:
224 #Sherpa(v2.2.1) (DSID 410250)
225 #PDG ID | Production Fraction
226 511 0.27243
227 521 0.273029
228 531 0.0893853
229 5000 0.365034
230 411 0.142884
231 421 0.379585
232 431 0.113005
233 4000 0.364526
234 */
235 infile = std::ifstream(filename);
236 std::map<unsigned int, float> charm;
237 std::map<unsigned int, float> bottom;
238 while (std::getline(infile, line)) {
239 std::istringstream iss(line);
240 if (line.rfind("#", 0) == 0 || line.empty()) {
241 continue;
242 }
243
244 int pdgId;
245 float fraction;
246 if (!(iss >> pdgId >> fraction)) {
247 ATH_MSG_ERROR("Invalid formatting: " << line);
248 return StatusCode::FAILURE;
249 }
250 if (pdgId == 4000 || pdgId < 500) {
251 ATH_MSG_DEBUG("Charm meson " << pdgId << " has a fraction of " << fraction);
252 charm[pdgId] = fraction;
253 } else {
254 ATH_MSG_DEBUG("Bottom meson " << pdgId << " has a fraction of " << fraction);
255 bottom[pdgId] = fraction;
256 }
257 }
258
259 // Close file
260 infile.close();
261
262 // Step 3: calculate the weight as `w = f(literature) / f(MC)'
263 // ________________________________________________________________
264
265 // charm weights
266 for (auto &sys : m_charmProdFractionWeights) {
267 for (auto &kv : charm) {
268 m_charmProdFractionWeights[sys.first][kv.first] /= kv.second;
269 }
270 }
271
272 // bottom weights
273 for (auto &sys : m_bottomProdFractionWeights) {
274 for (auto &kv : bottom) {
275 m_bottomProdFractionWeights[sys.first][kv.first] /= kv.second;
276 }
277 }
278
279 // Return gracefully
280 return StatusCode::SUCCESS;
281}
282
284 for (unsigned int i = 0; i < particle->nParents(); i++) {
285 if (particle->parent(i)->isHeavyHadron()) {
286 if (particle->parent(i)->isBottomHadron()) {
287 return true;
288 } else {
289 return fromBdecay(particle->parent(i));
290 }
291 }
292 }
293 return false;
294}
295
297 if (tp->nParents() < 1) {
298 return tp;
299 }
300
301 const xAOD::TruthParticle *out = nullptr;
302 for (unsigned int i = 0; i < tp->nParents(); i++) {
303 if (tp->parent(i) && (tp->parent(i)->absPdgId() == tp->absPdgId())) {
304 const xAOD::TruthParticle *initialParticle = getInitialParticle(tp->parent(i));
305 if (initialParticle && out && (initialParticle != out)) {
306 // this should never happen, but if it does crash the program
307 throw std::runtime_error("Contradictory information in the truth decay chain!");
308 }
309 out = initialParticle;
310 }
311 }
312
313 if (out) {
314 return out;
315 }
316 return tp;
317}
318
319float PMGHFProductionFractionTool::getWeight(const xAOD::TruthParticleContainer *truthParticles, const ParameterSet &prod_fracs) const {
320 // Clear vectors
321 std::set<const xAOD::TruthParticle *> bottomHadrons;
322 std::set<const xAOD::TruthParticle *> charmHadrons;
323
324 // Find truth particles hadrons
325 for (const xAOD::TruthParticle *tp : *truthParticles) {
326 // if (!(tp->status() == 2 || tp->status() == 1)) {
327 // continue;
328 // }
329 if (tp->isBottomHadron())
330 {
331 if (tp->pt() >= m_fiducialPtCut && std::abs(tp->eta()) < m_fiducialEtaCut) {
332 bottomHadrons.insert(getInitialParticle(tp));
333 }
334 }
335 if (tp->isCharmHadron()) {
336 if (!fromBdecay(getInitialParticle(tp)) && tp->pt() >= m_fiducialPtCut && std::abs(tp->eta()) < m_fiducialEtaCut) {
337 charmHadrons.insert(getInitialParticle(tp));
338 }
339 }
340 }
341
342 // The weight
343 float weight = 1.0;
344
345 // Calculate the charm weight
346 for (auto *tp : charmHadrons) {
347 int pdgId = tp->absPdgId();
348 float w = 1.0;
349 if (tp->isCharmMeson() && prod_fracs.charmWeights.find(pdgId) != prod_fracs.charmWeights.end()) {
350 w = prod_fracs.charmWeights.at(pdgId);
351 } else if (pdgId == 4122 || // Lambda_c+
352 pdgId == 4232 || // Xi_c+
353 pdgId == 4132 || // Xi_c0
354 pdgId == 4332) { // Omega_c0
355 w = prod_fracs.charmWeights.at(4000);
356 }
357 ATH_MSG_DEBUG("Charm weight for pdgId " << pdgId << ", pT " << tp->pt() << ", eta " << tp->eta() << ": " << w);
358 weight *= w;
359 }
360
361 // Calculate the bottom weight
362 for (auto *tp : bottomHadrons) {
363 int pdgId = tp->absPdgId();
364 float w = 1.0;
365 if (tp->isBottomMeson() && prod_fracs.bottomWeights.find(pdgId) != prod_fracs.bottomWeights.end()) {
366 w = prod_fracs.bottomWeights.at(pdgId);
367 } else if (pdgId == 5122 || // Lambda_b0
368 pdgId == 5132 || // Xi_b-
369 pdgId == 5232 || // Xi_b0
370 pdgId == 5332) { // Omega_b-
371 w = prod_fracs.bottomWeights.at(5000);
372 }
373 ATH_MSG_DEBUG("Bottom weight for pdgId " << pdgId << ", pT " << tp->pt() << ", eta " << tp->eta() << ": " << w);
374 weight *= w;
375 }
376 return weight;
377}
378
380 if (m_showerGenerator == "000000") { return 1.0; }
381 const ParameterSet *prod_fracs = nullptr;
382 ANA_CHECK_THROW (m_Parameters.get(sys, prod_fracs));
383 return getWeight(truthParticles, *prod_fracs);
384}
385
387 // MC Shower
388 ATH_MSG_INFO("The tool is currently configured to give weights for " << m_showerGenerator);
389
390 // Charm production fraction weights
391 ATH_MSG_INFO("Currently set weights for charm production fractions:");
392 for (auto const &generator : m_charmProdFractionWeights) {
393 ATH_MSG_INFO("sys: " << generator.first.name());
394 for (auto const &kv : generator.second) {
395 ATH_MSG_INFO(" - " << kv.first << " " << kv.second);
396 }
397 }
398
399 // Bottom production fraction weights
400 ATH_MSG_INFO("Currently set weights for bottom production fractions:");
401 for (auto const &generator : m_bottomProdFractionWeights) {
402 ATH_MSG_INFO("sys: " << generator.first.name());
403 for (auto const &kv : generator.second) {
404 ATH_MSG_INFO(" - " << kv.first << " " << kv.second);
405 }
406 }
407}
408
410 if (systConfig.size() > 1) {
411 ATH_MSG_ERROR("Multiple systematic variations in one SystematicsSet are not supported");
412 return StatusCode::FAILURE;
413 }
414
415 // set charm and bottom weights to nominal by default
417 ATH_MSG_ERROR("Nominal charm weights not found");
418 return StatusCode::FAILURE;
419 }
420
422 ATH_MSG_ERROR("Nominal bottom weights not found");
423 return StatusCode::FAILURE;
424 }
425
426 auto charmWeights = m_charmProdFractionWeights.at(CP::SystematicVariation(""));
427 auto bottomWeights = m_bottomProdFractionWeights.at(CP::SystematicVariation(""));
428
429 // if a sys variation is passed that affects the weights, choose the appropriate weight set
430 if (systConfig.size() == 1) {
431 const CP::SystematicVariation &current_variation = *(systConfig.begin());
432 if (m_charmProdFractionWeights.find(current_variation) != m_charmProdFractionWeights.end()) {
433 charmWeights = m_charmProdFractionWeights.at(current_variation);
434 }
435
436 if (m_bottomProdFractionWeights.find(current_variation) != m_bottomProdFractionWeights.end()) {
437 bottomWeights = m_bottomProdFractionWeights.at(current_variation);
438 }
439 }
440
441 // set the ParameterSet (weights set) for use in weight calculation
442 param.charmWeights = std::move(charmWeights);
443 param.bottomWeights = std::move(bottomWeights);
444
445 return StatusCode::SUCCESS;
446}
447
449{
450 if (m_showerGenerator == "000000") {
451 ATH_MSG_WARNING("The property `ShowerGenerator' was set to 000000. The tool will now return an empty SystematicsSet and dummy weights of 1.0");
452 return CP::SystematicSet();
453 }
455 ATH_MSG_ERROR("The production fraction weights were not properly initialized!");
456 throw std::runtime_error("Charm or bottom production fraction weight map is empty!");
457 }
459 for (const auto &kv : m_charmProdFractionWeights) {
460 if(!kv.first.empty())
461 result.insert(kv.first);
462 }
463 for (const auto &kv : m_bottomProdFractionWeights) {
464 if(!kv.first.empty())
465 result.insert(kv.first);
466 }
467
469 return result;
470}
471
472} // namespace PMGTools
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_ERROR(x)
#define ATH_MSG_INFO(x)
#define ATH_MSG_WARNING(x)
#define ATH_MSG_DEBUG(x)
#define ANA_CHECK(EXP)
check whether the given expression was successful
#define ANA_CHECK_THROW(EXP)
check whether the given expression was successful, throwing an exception on failure
static Double_t fs
std::string PathResolverFindCalibFile(const std::string &logical_file_name)
This module implements the central registry for handling systematic uncertainties with CP tools.
static SystematicRegistry & getInstance()
Get the singleton instance of the registry for the curren thread.
StatusCode registerSystematics(const IReentrantSystematicsTool &tool)
effects: register all the systematics from the tool
Class to wrap a set of SystematicVariations.
const_iterator begin() const
description: const iterator to the beginning of the set
size_t size() const
returns: size of the set
std::string m_calibrationAreaPath
Path to calibration area.
StatusCode readProductionFractionsFile(const std::string &, std::map< CP::SystematicVariation, std::map< unsigned int, float > > &)
Read production fractions from input file.
PMGHFProductionFractionTool(const std::string &name)
Create a proper constructor for Athena.
virtual StatusCode initialize() override
Function initialising the tool.
const xAOD::TruthParticle * getInitialParticle(const xAOD::TruthParticle *tp) const
Loops back the decay chain through particles with the same pdgId (e.g. photon emission)
std::map< CP::SystematicVariation, std::map< unsigned int, float > > m_charmProdFractionWeights
Charm production fraction weights.
std::string m_showerGenerator
MC Shower generator software (valid options: Pythia8)
float m_fiducialPtCut
The fiducial charm/bottom pT cut (in GeV)
std::map< std::string, std::string > m_showerGeneratorMap
MC Shower generator map.
std::string m_charmFilename
Input file with charm production fractions.
bool fromBdecay(const xAOD::TruthParticle *particle) const
Checks if a particle originates from a bottom decay.
CP::SystematicsCache< ParameterSet > m_Parameters
The SystematicsCache object.
virtual float getSysWeight(const xAOD::TruthParticleContainer *truthParticles, const CP::SystematicSet &sys) const override
Implements interface from ISysTruthWeightTool.
StatusCode setSystematicVariation(const CP::SystematicSet &systConfig, ParameterSet &param) const
calculate the parameter set for the given systematic
std::string m_showerGeneratorMapFile
MC Shower generator map file name.
float getWeight(const xAOD::TruthParticleContainer *truthParticles, const ParameterSet &prod_fracs) const
virtual CP::SystematicSet affectingSystematics() const override
Which systematics have an effect on the tool's behaviour?
std::string m_bottomFilename
Input file with bottom production fractions.
void printCurrentProdFractions() const
Print the current production fractions.
float m_fiducialEtaCut
The fiducial charm/bottom eta cut.
std::map< CP::SystematicVariation, std::map< unsigned int, float > > m_bottomProdFractionWeights
Bottom production fraction weights.
AsgMetadataTool(const std::string &name)
Normal ASG tool constructor with a name.
Tool providing sample cross-sections and k-factors etc.
TruthParticle_v1 TruthParticle
Typedef to implementation.
TruthParticleContainer_v1 TruthParticleContainer
Declare the latest version of the truth particle container.