ATLAS Offline Software
Loading...
Searching...
No Matches
Generic4VecCorrection.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
8
9#include "TH2.h"
10#include <TEnv.h>
11#include "TFile.h"
12#include "TObjString.h"
13#include <algorithm>
14#include <fstream>
15
16Generic4VecCorrection::Generic4VecCorrection(const std::string& name, TEnv* config,
17 const TString &jetAlgo, const TString &calibAreaTag, const TString & forceCalibFile,
18 JET_CORRTYPE correctionType, const TString & mcCampaign, const TString & simFlavour, int mcDSID,
19 const TString &generatorsInfo)
21 m_config(config), m_jetAlgo(jetAlgo), m_calibAreaTag(calibAreaTag), m_correctionType(correctionType),
22 m_simFlavour(simFlavour), m_mcDSID(mcDSID), m_generatorsInfo(generatorsInfo), m_mcCampaign(mcCampaign), m_forceCalibFile(forceCalibFile), m_skipCorrection(false), m_correctionFilePath("")
23{ }
24
30
33
35{
36 ATH_MSG_INFO("Initializing Generic4VecCorrection correction tool.");
37
38 if (!m_config){
39 ATH_MSG_FATAL("Config file not specified. Aborting.");
40 return StatusCode::FAILURE;
41 }
42
43 // Set correction-specific information & retrieve response histogram(s)
44 std::string algo_type, default_OutJetScale;
46 algo_type = "JPS_PtResidual";
47 default_OutJetScale = "JetPtResidualScaleMomentum";
49 // Save etaAxis to assist in avoiding eta-interpolation
50 m_etaAxis = *(m_only_correction_2D->GetYaxis());
51
53 algo_type = "JPS_MC2MC";
54 default_OutJetScale = "JetMC2MCScaleMomentum";
55 bool isMC = TString(m_simFlavour).Contains("FullG4",TString::kIgnoreCase) || TString(m_simFlavour).Contains("ATLFAST3",TString::kIgnoreCase);
56 if (m_simFlavour == ""){
57 ATH_MSG_WARNING("No simFlavour metadata available for this sample! Assuming it is MC, but this could cause an error if your sample is not listed in MC2MC_exceptions_DSID.json");
58 isMC = true;
59 }
60 if(!isMC){
61 if( m_forceCalibFile=="" ){
62 ATH_MSG_INFO("Will not apply JPS_MC2MC to this Data file.");
63 m_skipCorrection = true;
64 return StatusCode::SUCCESS;
65 } else {
66 ATH_MSG_WARNING("Metadata does not indicate this is MC (assuming data), but ForceCalibFile is set so will apply JPS_MC2MC correction anyway.");
67 }
68 }
70
72 algo_type = "JPS_FastSim";
73 default_OutJetScale = "JetFastSimScaleMomentum";
74
75 bool isAF3 = TString(m_simFlavour).Contains("ATLFAST3",TString::kIgnoreCase);
76 if(!isAF3){
77 if( m_forceCalibFile==""){
78 ATH_MSG_INFO("Will not apply JPS_FastSim to this Data or FullSim file.");
79 m_skipCorrection = true;
80 return StatusCode::SUCCESS;
81 } else {
82 ATH_MSG_WARNING("ForceCalibFile is set for a data or FullSim file, will apply JPS_FastSim correction anyway.");
83 }
84 }
86
87 } else{
88 ATH_MSG_FATAL("Generic4VecCorrection is incorrectly configured. Aborting.");
89 return StatusCode::FAILURE;
90 }
91
92 // Get the starting and ending jet scales
93 m_inJetScale = m_config->GetValue( (algo_type+".InJetScale").c_str(), "Default");
94 m_outJetScale = m_config->GetValue( (algo_type+".OutJetScale").c_str(), default_OutJetScale.c_str());
95 if ( m_inJetScale != "Default" || m_outJetScale != default_OutJetScale ){
96 ATH_MSG_WARNING(algo_type << " is configured to use custom jet scale input " << m_inJetScale << "and/or custom jet scale output " << m_outJetScale << ", this is expert-level only!");
97 }
98
99 ATH_MSG_INFO("Starting " << algo_type << " correction from jet scale " << m_inJetScale << " and writing out jet scale " << m_outJetScale << ", using input file " << m_correctionFilePath);
100 return StatusCode::SUCCESS;
101}
102
103// Get correctionFactor from requested histogram
104StatusCode Generic4VecCorrection::readHisto(float& correctionFactor, TH2* h_correction_2D, float x, float y) const
105{
106 // If we are outside histogram boundaries, silently take value of closest bin
107 const float minX = h_correction_2D->GetXaxis()->GetBinLowEdge(1);
108 const float maxX = h_correction_2D->GetXaxis()->GetBinLowEdge(h_correction_2D->GetNbinsX()+1);
109 const float minY = h_correction_2D->GetYaxis()->GetBinLowEdge(1);
110 const float maxY = h_correction_2D->GetYaxis()->GetBinLowEdge(h_correction_2D->GetNbinsY()+1);
111 if ( x >= maxX )
112 x = maxX - 1.e-6;
113 else if ( x <= minX )
114 x = minX + 1.e-6;
115 if ( y >= maxY )
116 y = maxY - 1.e-6;
117 else if ( y <= minY )
118 y = minY + 1.e-6;
119
120 // Interpolate final correctionFactor
121 correctionFactor = h_correction_2D->Interpolate(x,y);
122
123 return StatusCode::SUCCESS;
124}
125
126// Perform 4Vec correction for a jet
128{
129 (void)jetEventInfo; //Unused
130
131 // Skip correction if we've determined it is not to be applied to this input file
133 return StatusCode::SUCCESS;
134
135 xAOD::JetFourMom_t calibP4;
136 if (m_inJetScale == "Default")
137 calibP4 = jet.jetP4();
138 else
139 calibP4 = jet.jetP4(m_inJetScale);
140
141 float correctionFactor = 1.0;
142
143 TH2* h_correction_2D = nullptr;
144 float this_pt, this_eta;
146 this_pt = jet.pt()/1000.;
147 static const SG::ConstAccessor<float> DetectorEtaAcc ("DetectorEta");
148 this_eta = DetectorEtaAcc(jet);
149 h_correction_2D = m_only_correction_2D;
150 //Make sure we STAY within the bin boundaries when performing the interpolation hack
151 const float minY = m_etaAxis.GetBinLowEdge(1);
152 const float maxY = m_etaAxis.GetBinLowEdge(m_etaAxis.GetNbins()+1);
153 this_eta = std::clamp(this_eta, minY, maxY);
154 // PtResidual should not interpolate across eta bins, so set this_eta to the center of its histogram bin
155 int eta_bin = m_etaAxis.FindBin(this_eta);
156 this_eta = m_etaAxis.GetBinCenter(eta_bin);
157
159 this_pt = jet.pt()/1000.;
160 this_eta = fabs(jet.rapidity());
161 h_correction_2D = m_only_correction_2D;
162 //Make sure we STAY within the bin boundaries when performing the interpolation hack
163 const float minY = m_etaAxis.GetBinLowEdge(1);
164 const float maxY = m_etaAxis.GetBinLowEdge(m_etaAxis.GetNbins()+1);
165 this_eta = std::clamp(this_eta, minY, maxY);
166 // AF3 should not interpolate across eta (Y) bins, so set this_eta to the center of its histogram bin
167 int eta_bin = m_etaAxis.FindBin(this_eta);
168 this_eta = m_etaAxis.GetBinCenter(eta_bin);
170 this_pt = jet.pt()/1000.;
171 this_eta = fabs(jet.rapidity());
172
173 static const SG::ConstAccessor<int> PartonTruthLabelIDAcc ("PartonTruthLabelID");
174 if(!PartonTruthLabelIDAcc.isAvailable(jet))
175 return StatusCode::SUCCESS;
176 int jet_PID = abs(PartonTruthLabelIDAcc(jet));
177
178 // If this jet_PID is in the map, get its h_correction_2D and find the correctionFactor
179 auto correction_from_map = m_correctionHists.find(jet_PID);
180 if (correction_from_map != m_correctionHists.end()){
181 h_correction_2D = correction_from_map->second;
182 }
183 }
184
185 if (h_correction_2D){
186 ATH_CHECK( readHisto(correctionFactor, h_correction_2D, this_pt, this_eta) );
187 }
188 // Apply the correction and set it in the jet EDM
189 calibP4 *= correctionFactor;
190 jet.setAttribute<xAOD::JetFourMom_t>(m_outJetScale,calibP4);
191 jet.setJetP4(calibP4);
192
193 return StatusCode::SUCCESS;
194}
195
197{
198 std::string algo_type, calibFilePrepend, corrHistName;
200 algo_type = "JPS_PtResidual";
201 calibFilePrepend = "PtResidual";
202 corrHistName = "h_respMap_recoPt_DetEta";
204 algo_type = "JPS_FastSim";
205 calibFilePrepend = "AF3";
206 corrHistName = "h_respMap_recoPt_recoY";
207 }
208
209 // If CalibFile is set in tool configuration, we will force that generator correction.
210 // Used if metadata is not available for this file or for expert-level tests
211 TString CalibFile;
212 if (m_forceCalibFile != ""){
213 CalibFile = m_forceCalibFile;
214 ATH_MSG_WARNING("Have forced " << algo_type << " CalibFile to be " << m_forceCalibFile);
215
216 } else {
217 // Recommended method to build the correct CalibFile
218 std::string CalibFileTag = m_config->GetValue( (algo_type+".CalibFileTag").c_str(), "");
219 if( CalibFileTag == "" || m_jetAlgo == "" || m_mcCampaign == ""){
220 ATH_MSG_FATAL("At least one of the required parameters is not set, please check m_mcCampaign (" << m_mcCampaign << "), m_jetAlgo (" << m_jetAlgo << "), and " << algo_type << ".CalibFileTag (" << CalibFileTag <<")");
221 return StatusCode::FAILURE;
222 }
223 CalibFile.Append(m_calibAreaTag+"/CalibrationFactors/"+calibFilePrepend+"_"+m_mcCampaign+"_"+m_jetAlgo+"_"+CalibFileTag+".root");
224 }
225
227 if(m_correctionFilePath == ""){
228 ATH_MSG_FATAL("PathResolverFindCalibFile cannot find path to " << CalibFile);
229 return StatusCode::FAILURE;
230 }
231 std::unique_ptr<TFile> inputFile(TFile::Open(m_correctionFilePath.c_str()));
232 if (!inputFile || inputFile->IsZombie()){
233 ATH_MSG_FATAL("Cannot open " << algo_type << "'s CalibFile, even though the m_correctionFilePath exists: " << CalibFile);
234 return StatusCode::FAILURE;
235 }
236
237 m_only_correction_2D = (TH2*)inputFile->Get( corrHistName.c_str() );
239 ATH_MSG_FATAL("Failed to retrieve histogram: " << corrHistName);
240 return StatusCode::FAILURE;
241 }
242 m_only_correction_2D->SetDirectory(0);
243
244 inputFile->Close();
245 return StatusCode::SUCCESS;
246}
247
248StatusCode Generic4VecCorrection::load_json(nlohmann::json& json_object, const std::string& json_filepath) const
249{
250 std::string full_path = PathResolverFindCalibFile(json_filepath);
251 std::ifstream json_stream(full_path);
252 if( json_stream.peek() == std::ifstream::traits_type::eof() )
253 return StatusCode::FAILURE;
254
255 json_stream >> json_object;
256 return StatusCode::SUCCESS;
257}
258
259
260
262{
263
264 std::string showerModel;
266
267 // If CalibFile is set, we will force that generator correction. This is expert-level functionality
268 TString MC2MC_CalibFile;
269 if (m_forceCalibFile != ""){
270 MC2MC_CalibFile = m_forceCalibFile;
271 ATH_MSG_WARNING("For sample of showerModel " << showerModel << ", have forced MC2MC CalibFile to be " << m_forceCalibFile);
272
273 // Set showerModel to the requested version
274 TObjArray *CalibFile_fields = MC2MC_CalibFile.Tokenize("_");
275 float n_fields = CalibFile_fields->GetEntries();
276 std::string new_showerModel = ((TObjString *)(CalibFile_fields->At(n_fields-1)))->String().Data();
277 new_showerModel.resize(new_showerModel.find(".root")); //Remove .root
278 showerModel = std::move(new_showerModel);
279
280 } else {
281 // Recommended method to build the correct CalibFile
282 std::string MC2MC_CalibFileTag = m_config->GetValue("JPS_MC2MC.CalibFileTag","");
283 if( MC2MC_CalibFileTag == "" || m_jetAlgo == "" || m_mcCampaign == "" || showerModel == ""){
284 ATH_MSG_FATAL("At least one of the required parameters is not set, please check m_mcCampaign (" << m_mcCampaign << "), m_jetAlgo (" << m_jetAlgo << "), JPS_MC2MC.CalibFileTag (" << MC2MC_CalibFileTag <<"), and showerModel (" << showerModel <<")");
285 return StatusCode::FAILURE;
286 }
287 MC2MC_CalibFile.Append(m_calibAreaTag+"/CalibrationFactors/MC2MC_"+m_mcCampaign+"_"+m_jetAlgo+"_"+MC2MC_CalibFileTag+"_"+showerModel+".root");
288 }
289 // If the found / requested showerModel is the original Pythia used for calibrations, or was specifically set to None, skip the MC2MC correction
290 if (showerModel.starts_with("Pythia") || showerModel.starts_with("None")){
291 m_skipCorrection = true;
292 ATH_MSG_INFO("Will not perform MC2MC correction for this sample (Pythia or forced to None), but will write out the redundant jet scale " << m_outJetScale);
293 return StatusCode::SUCCESS;
294 }
295 m_correctionFilePath = PathResolverFindCalibFile(MC2MC_CalibFile.Data());
296 if(m_correctionFilePath == ""){
297 ATH_MSG_FATAL("PathResolverFindCalibFile cannot find path to MC2MC CalibFile: " << MC2MC_CalibFile);
298 return StatusCode::FAILURE;
299 }
300 std::unique_ptr<TFile> inputFile(TFile::Open(m_correctionFilePath.c_str()));
301 if (!inputFile || inputFile->IsZombie()){
302 ATH_MSG_FATAL("Cannot open MC2MC CalibFile, even though the m_correctionFilePath exists: " << MC2MC_CalibFile);
303 return StatusCode::FAILURE;
304 }
305 // Get list of jet PIDs to perform correction on
306 std::vector<int> considered_PIDs = {1,2,3,21}; //Always correct u/d/s/g
307 bool doCjetCorrection = m_config->GetValue("JPS_MC2MC.doCjetCorrection",true);
308 if(doCjetCorrection)
309 considered_PIDs.push_back(4);
310 bool doBjetCorrection = m_config->GetValue("JPS_MC2MC.doBjetCorrection",true);
311 if(doBjetCorrection)
312 considered_PIDs.push_back(5);
313
314 // Load all the requested MC2MC correction histograms into the correction map
315 for (auto this_PID : considered_PIDs){
316 TString this_hist_name;
317 if(this_PID == 1 || this_PID == 2 || this_PID == 3){
318 this_hist_name = "h_respMap_recoPt_recoY_q";
319 } else if(this_PID == 4){
320 this_hist_name = "h_respMap_recoPt_recoY_c";
321 } else if(this_PID == 5){
322 this_hist_name = "h_respMap_recoPt_recoY_b";
323 } else if(this_PID == 21){
324 this_hist_name = "h_respMap_recoPt_recoY_g";
325 } else {
326 ATH_MSG_FATAL("Requested PID " << this_PID << " is not supported for MC2MC correction, please contact JetETMiss.");
327 return StatusCode::FAILURE;
328 }
329 TH2* this_hist = (TH2*)inputFile->Get(this_hist_name);
330 if (!this_hist){
331 ATH_MSG_FATAL("Failed to retrieve histogram: " << this_hist_name);
332 return StatusCode::FAILURE;
333 }
334 this_hist->SetName( ("h_respMap_recoPt_recoY_"+std::to_string(this_PID)).c_str() );
335 this_hist->SetDirectory(0);
336 m_correctionHists.insert( std::make_pair(this_PID, this_hist) );
337 }
338 inputFile->Close();
339
340 return StatusCode::SUCCESS;
341}
342
343// For MC2MC, parse showerModel from generatorsInfo, of form "Powheg(v.06-02)+Herwig7(v.7.2.3p2)+EvtGen(v.2.1.1)""
344StatusCode Generic4VecCorrection::parse_showerModel(std::string& showerModel, int mcDSID, TString generatorsInfo) const
345{
346
347 // Check if an exception to the showerModel exists for this DSID
348 nlohmann::json MC2MC_exceptions_DSID;
349 ATH_CHECK( load_json(MC2MC_exceptions_DSID, "JetCalibTools/MC2MC_exceptions_DSID.json") );
350 if( MC2MC_exceptions_DSID.contains(std::to_string(mcDSID)) ){
351 showerModel = MC2MC_exceptions_DSID[std::to_string(mcDSID)];
352 ATH_MSG_INFO("Sample DSID " << mcDSID << " is in the MC2MC_exceptions_DSID list, will be forcing the showerModel " << showerModel);
353 return StatusCode::SUCCESS;
354 }
355
356 if(generatorsInfo == ""){
357 showerModel="";
358 ATH_MSG_DEBUG("No generatorsInfo string provided, cannot parse showerModel.");
359 return StatusCode::SUCCESS;
360 }
361
362 // Parse shower model from MC sample generatorInfo
363 TObjArray *generatorsInfo_fields = generatorsInfo.Tokenize("+");
364 float n_fields = generatorsInfo_fields->GetEntries();
365 std::string this_substr = ((TObjString *)(generatorsInfo_fields->At(n_fields-1)))->String().Data();
366
367 // Remove any trailing afterburners
368 while( n_fields > 0 &&
369 (this_substr.starts_with("EvtGen") ||
370 this_substr.starts_with("Photos") ||
371 this_substr.starts_with("Tauola") ) ) {
372 generatorsInfo_fields->RemoveAt(n_fields-1);
373 n_fields--;
374
375 if ( n_fields == 0 ){
376 ATH_MSG_FATAL("No valid PS/Had model found in generatorsInfo string: " << generatorsInfo);
377 return StatusCode::FAILURE;
378 }
379
380 this_substr = ((TObjString *)(generatorsInfo_fields->At(n_fields-1)))->String().Data();
381 }
382 std::string full_pshadInfo = this_substr;
383
384 // Find generator type and set default Parton Shower / Hadronization models
385 std::string genType = "";
386 std::string psType = "";
387 std::string hadType = "";
388 std::string version = "";
389 if (this_substr.starts_with("Herwigpp")){
390 genType = "Herwigpp";
391 psType = "angular";
392 hadType = "cluster";
393 } else if (this_substr.starts_with("Herwig")){
394 genType = "Herwig";
395 psType = "angular";
396 hadType = "cluster";
397 } else if (this_substr.starts_with("Sherpa")){
398 genType = "Sherpa";
399 psType = "dipole";
400 hadType = "cluster";
401 } else if (this_substr.starts_with("Pythia8B")){
402 genType = "PythiaB";
403 psType = "dipole";
404 hadType = "cluster";
405 version += "8";
406 } else if (this_substr.starts_with("Pythia")){
407 genType = "Pythia";
408 psType = "dipole";
409 hadType = "cluster";
410 } else {
411 ATH_MSG_FATAL("No valid generator type found in generatorsInfo string: " << generatorsInfo);
412 return StatusCode::FAILURE;
413 }
414
415 // Parse version number of the generator
416 this_substr = this_substr.substr(this_substr.find("(v.") + 3); // Remove up to first version number in e.g. Herwig7(v.7.2.3p2)
417 if( full_pshadInfo.starts_with("Pythia8") && !this_substr.starts_with("8")){ // Exception for out-of-order Pythia version
418 version += "8";
419 }
420
421 // Remove any trailing characters after the version number e.g. (v.7.2.3p2)
422 std::vector<std::string> version_exceptions = {"alpha", "p", "bbb", "atlas", "beta", ")"};
423 for (const auto& exception : version_exceptions) {
424 if (this_substr.find(exception) != std::string::npos) {
425 this_substr.resize(this_substr.find(exception));
426 }
427 }
428
429 // Remove all periods so that only numbers remain
430 this_substr.erase(std::remove(this_substr.begin(), this_substr.end(), '.'), this_substr.end());
431
432 // What is left is the version tag
433 version += this_substr;
434
435 // Build the full showerModel tag
436 showerModel = genType+"-"+version+"-"+psType+"-"+hadType;
437
438 // Check if a remap of this showerModel version is requested, if so use it
439 // We do this even for mapped_DSID, to allow for simple swapping of showerModel across all exceptions
440 nlohmann::json MC2MC_showerRemap;
441 ATH_CHECK( load_json(MC2MC_showerRemap, "JetCalibTools/MC2MC_showerRemap.json") );
442
443 if( MC2MC_showerRemap.contains( genType+"-"+version+"-"+psType+"-"+hadType ) ){
444 std::string replaced_showerModel = MC2MC_showerRemap[ genType+"-"+version+"-"+psType+"-"+hadType ];
445 ATH_MSG_INFO("Sample with identified showerModel " << genType+"-"+version+"-"+psType+"-"+hadType << " is in the MC2MC_showerRemap list, will be forcing the showerModel " << replaced_showerModel);
446 showerModel = std::move(replaced_showerModel);
447 } else if (MC2MC_showerRemap.contains( genType+"-"+version ) ){
448 std::string replaced_showerModel = MC2MC_showerRemap[ genType+"-"+version ];
449 replaced_showerModel += "-"+psType+"-"+hadType;
450 ATH_MSG_INFO("Sample with identified showerModel " << genType+"-"+version+"-"+psType+"-"+hadType << " is in the MC2MC_showerRemap list, will be forcing the showerModel " << replaced_showerModel);
451 showerModel = std::move(replaced_showerModel);
452 }
453
454 return StatusCode::SUCCESS;
455}
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_DEBUG(x,...)
#define ATH_MSG_WARNING(x,...)
#define ATH_MSG_INFO(x,...)
#define ATH_MSG_FATAL(x,...)
std::string PathResolverFindCalibFile(const std::string &logical_file_name)
#define y
#define x
const std::string m_calibAreaTag
const std::string m_generatorsInfo
const std::string m_simFlavour
virtual StatusCode initialize() override
StatusCode parse_showerModel(std::string &showerModel, int mcDSID, TString generatorsInfo) const
const std::string m_mcCampaign
StatusCode readHisto(float &correctionFactor, TH2 *h_correction_2D, float x, float y) const
StatusCode load_json(nlohmann::json &json_object, const std::string &json_filepath) const
const std::string m_forceCalibFile
virtual StatusCode calibrate(xAOD::Jet &jet, JetEventInfo &) const override
std::map< int, TH2 * > m_correctionHists
JetCalibrationStep(const char *name="JetCalibrationStep")
Helper class to provide constant type-safe access to aux data.
bool isAvailable(const ELT &e) const
Test to see if this variable exists in the store.
DataModel_detail::iterator< DVL > remove(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end, const T &value)
Specialization of remove for DataVector/List.
Jet_v1 Jet
Definition of the current "jet version".
ROOT::Math::LorentzVector< ROOT::Math::PtEtaPhiM4D< double > > JetFourMom_t
Base 4 Momentum type for Jet.
Definition JetTypes.h:17