ATLAS Offline Software
Loading...
Searching...
No Matches
nnlo_reweighter.py
Go to the documentation of this file.
1# Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
2
3import glob
4import os
5import re
6import shutil
7from AthenaCommon import Logging
8from ...decorators import timed
9from ...utility import LHE, ProcessManager, SingleProcessThread
10from xml.etree import ElementTree
11
12
13
14logger = Logging.logging.getLogger("PowhegControl")
15
16
17@timed("NNLO reweighter")
18def NNLO_reweighter(process, powheg_LHE_output):
19 """! Move output to correctly named file.
20
21 @param process External NNLOPS process.
22 @param powheg_LHE_output Name of LHE file produced by PowhegBox.
23
24 @author James Robinson <james.robinson@cern.ch>
25 """
26 process.expose()
27 logger.info("Initialising NNLO reweighting")
28 output_LHE_name = "{}.NNLO".format(powheg_LHE_output)
29 aux_directory = os.path.join(*(process.executable.split("POWHEG")[:-1] + ["AuxFiles"]))
30
31 # Get NNLO executable
32 logger.info("Using NNLO reweighter at {}".format(process.executable))
33
34 # Strip comment strings from input LHEF file - reweighter will crash otherwise
35 logger.info("Removing comments from input LHE file")
36 os.system(r"sed -i 's/\!.*$//g' {}".format(powheg_LHE_output))
37
38 acquire_NNLO_inputs(aux_directory, process.NNLO_reweighting_inputs)
39 run_NNLO_executable(process, powheg_LHE_output, output_LHE_name)
40 reformat_NNLO_events(process, powheg_LHE_output, output_LHE_name)
41
42def rename_weights_toNNLOPS(input_filename, output_filename, powheg_LHE_output):
43 # Define the pattern to extract information from the weight name
44 weight_pattern = re.compile(r'MUR(\d+(\.\d+)?)_MUF(\d+(\.\d+)?)_PDF(\d+)')
45
46 # Define the function to create the new weight name
47 def create_new_name(match):
48 mur_value = match.group(1)
49 muf_value = match.group(3)
50 pdf_number = match.group(5)
51 return f'renscfact={mur_value} facscfact={muf_value} lhapdf={pdf_number}'
52
53 # Create a backup of the original LHE file
54 shutil.copy(input_filename, 'pwgevents.lhe_beforeNNLOPSreweighting')
55
56 # Open the input and output files
57 with open(input_filename, 'r') as input_file, open(output_filename, 'w') as output_file:
58 for line in input_file:
59 # Check if the line contains a weight to be renamed
60 if '<weight id' in line:
61 # Extract the weight name using regex
62 match = weight_pattern.search(line)
63 if match:
64 # Create the new weight name
65 new_name = create_new_name(match)
66 # Replace the old weight name with the new name
67 line = line.replace(match.group(0), new_name)
68 elif '>nominal</weight>' in line:
69 # Rename 'nominal' to 'default'
70 line = line.replace('nominal', 'default')
71 # Write the line to the output file
72 output_file.write(line)
73
74 shutil.move(output_filename, powheg_LHE_output)
75
76def acquire_NNLO_inputs(aux_directory, NNLO_reweighting_inputs):
77 """! Get reweighting files from AuxFiles directory if not provided with job."""
78 for NNLO_reweighting_file_name in NNLO_reweighting_inputs.values():
79 logger.info("Looking for configuration file {}...".format(NNLO_reweighting_file_name))
80 if os.path.isfile(NNLO_reweighting_file_name):
81 logger.info("... found locally")
82 else:
83 try:
84 shutil.copy(glob.glob("{}/*/{}".format(aux_directory, NNLO_reweighting_file_name))[0], NNLO_reweighting_file_name)
85 logger.info("... found in Powheg OTF auxiliary directory")
86 except (OSError, IndexError):
87 logger.warning("... NOT found!")
88 logger.warning("(1) if you generated this file then please ensure it is visible to Powheg on-the-fly.")
89 logger.warning("(2) if you expected Powheg on-the-fly to know about this file, please submit a bug report.")
90 raise IOError("NNLO reweighting inputs not found.")
91
92
93def run_NNLO_executable(process, powheg_LHE_output, output_LHE_name):
94 """! Run the NNLO executable."""
95 # Run NNLOPS
96 NNLO_executable = process.executable
97 print("eA minnlo? ",NNLO_executable)
98 if "nnlopsreweighter" in NNLO_executable:
99 process.NNLO_weight_list = construct_NNLOPS_weight_list(process.NNLO_output_weights, powheg_LHE_output)
100 rename_weights_toNNLOPS( "pwgevents.lhe", "pwgevents.lhe_nnlops",powheg_LHE_output)
101 run_NNLOPS_executable(NNLO_executable, process.NNLO_reweighting_inputs, process.NNLO_weight_list, powheg_LHE_output, output_LHE_name)
102 # Run DYNNLO
103 elif "minnlo" in NNLO_executable:
104 run_DYNNLO_executable(NNLO_executable, process.NNLO_reweighting_inputs, powheg_LHE_output, output_LHE_name)
105
106
107def run_NNLOPS_executable(NNLO_executable, NNLO_reweighting_inputs, NNLO_weight_list, powheg_LHE_output, output_LHE_name):
108 """! Run NNLOPSreweighter with appropriate run card."""
109 # Prepare the nnlopsreweighter runcard
110 logger.info("Constructing NNLOPS run card")
111 with open("nnlopsreweighter.input", "w") as f_input_card:
112 # Name of the input LHE file
113 f_input_card.write("lhfile {}\n\n".format(powheg_LHE_output))
114
115 # Header of "nnlofiles" followed by a quoted label and the name of a HNNLO output file.
116 f_input_card.write("nnlofiles\n")
117 for label, NNLO_reweighting_file_name in NNLO_reweighting_inputs.items():
118 f_input_card.write("'{}' {}\n".format(label, NNLO_reweighting_file_name))
119 f_input_card.write("\n")
120
121 # NNLOPS weights, in LHEv3 format
122 # Description line tells reweighter how to calculate weights:
123 # *) loops through the weight IDs in the LHEF file and through the labels of the nnlofiles.
124 # *) if description contains a weight-label and an nnlofile-label:
125 # - compute new weight by reweighting the corresponding weights in the
126 # input file with the result from the nnlofile
127 f_input_card.write("<initrwgt>\n")
128 f_input_card.write("<weightgroup name='NNLOPS'>\n")
129 for _, weight_ID, weight_name in NNLO_weight_list:
130 f_input_card.write("<weight id='{}'> {} </weight>\n".format(weight_ID, weight_name))
131 f_input_card.write("</weightgroup>\n")
132 f_input_card.write("</initrwgt>\n")
133
134 # Run the executable until finished
135 if not os.path.isfile(NNLO_executable):
136 raise OSError("NNLO reweighting executable {} not found!".format(NNLO_executable))
137 manager = ProcessManager([SingleProcessThread(NNLO_executable)])
138 while manager.monitor():
139 pass
140
141 # Rename output
142 shutil.move("pwgevents.lhe.nnlo", output_LHE_name)
143
144
145def run_DYNNLO_executable(NNLO_executable, NNLO_reweighting_inputs, powheg_LHE_output, output_LHE_name):
146 """! Run DYNNLOPS reweighter with appropriate arguments."""
147 # Stage 1 - produce MINLO-W*-denom.top files
148 stage_1_command = [NNLO_executable, powheg_LHE_output, len(NNLO_reweighting_inputs)] + list(NNLO_reweighting_inputs.values())
149 logger.info("Running reweighting stage 1: denominator calculation")
150 manager = ProcessManager([SingleProcessThread(stage_1_command)])
151 while manager.monitor():
152 pass
153
154 # Stage 2 - produce MINLO-W*-denom.top files
155 stage_2_command = stage_1_command + ["MINLO-W{}-denom.top".format(idx) for idx in range(1, len(NNLO_reweighting_inputs) + 1)]
156 logger.info("Running reweighting stage 2: reweighting with pre-calculated denominators")
157 manager = ProcessManager([SingleProcessThread(stage_2_command)])
158 while manager.monitor():
159 pass
160
161 # Rename output
162 shutil.move("pwgevents.lhe-nnlo", output_LHE_name)
163
164
165def reformat_NNLO_events(process, powheg_LHE_output, output_LHE_name):
166 """! Reformat NNLOPS and DYNNLO events."""
167 shutil.move(powheg_LHE_output, "{}.NLO".format(powheg_LHE_output))
168 logger.info("Reformatting NNLO reweighting output")
169 NNLO_executable = process.executable
170 # Run NNLOPS
171 if "nnlopsreweighter" in NNLO_executable:
172 reformat_NNLOPS_events(process.NNLO_weight_list, powheg_LHE_output, output_LHE_name)
173 # Run DYNNLO
174 elif "minnlo" in NNLO_executable:
175 reformat_DYNNLO_events(powheg_LHE_output, output_LHE_name)
176
177
178def reformat_NNLOPS_events(NNLO_weight_list, powheg_LHE_output, output_LHE_name):
179 """! Reformat NNLOPS events to fit ATLAS conventions."""
180 logger.info("Renaming NNLO weights")
181 # Open the input and output files
182 with open(output_LHE_name, 'r') as infile, open(powheg_LHE_output, 'w') as outfile:
183 # Initialize variables to store weight IDs and mappings
184 # These are used to set the value of wgt id=? for each event in the file
185 weight_ids = []
186 weight_mappings = {}
187
188 # Initialize a flag to indicate if we are inside a weight block or headerblock
189 inside_weight_block = False
190 weight_number = 0
191 weight_number_NNLO = 0
192
193 # Process each line in the input file
194 for line in infile:
195 # Check if we are inside a weight block
196 if inside_weight_block:
197 # Check for the end of the weight block
198 if '</weights>' in line:
199 inside_weight_block = False
200 weight_number =0
201 # Replace </weights> with </rwgt>
202 line = line.replace('</weights>', '</rwgt>')
203 else:
204 # Extract the weight value and format it
205 weight_value = line.strip()
206 if weight_ids:
207 # Get the corresponding weight ID
208 weight_id = weight_ids[weight_number]
209 weight_number += 1
210 # Replace <weights> with <wgt id='weight_id'>
211 line = f'<wgt id="{weight_id}">{weight_value}</wgt>\n'
212 else:
213 # Check for the start of a weight block
214 if '<weights>' in line:
215 inside_weight_block = True
216 # Replace <weights> with <rwgt>
217 line = line.replace('<weights>', '<rwgt>')
218 elif '#rwgt' in line:
219 continue
220 # Check if the line contains weight definitions in the header
221 elif '<weight id=' in line:
222 if 'default</weight>' in line:
223 match = re.search(r'weight id=\'(\d+)\'\s*>(.*?)<', line)
224 weight_id = match.group(1)
225 line = line.replace('default', 'nominal')
226 weight_ids.append(weight_id)
227 elif '<weight id=\'nnlops-' in line:
228 #rename all NNLOPS weights and remove spaces, strings from the name
229 match = re.search(r"<weight id='(.*?)'\s>\s(.*?)\s<", line)
230 weight_id_NNLO = match.group(1)
231 weight_name = match.group(2)
232 line = line.replace(weight_id_NNLO, str(NNLO_weight_list[weight_number_NNLO][0]))
233 # Remove single quotes and "and" from the weight name
234 renamed_name = weight_name.replace("'", "")
235 # Replace spaces between words with underscores
236 renamed_name = renamed_name.replace(" ", "_")
237 line = line.replace(weight_name, renamed_name)
238 weight_number_NNLO += 1
239 # Extract the weight ID and name using regex
240 else:
241 match = re.search(r'weight id=\'(\d+)\'\s*>(.*?)<', line)
242 if match:
243 weight_id = match.group(1)
244 weight_name = match.group(2)
245 weight_ids.append(weight_id)
246 # Rename the weight name
247 renamed_name = re.sub(r'renscfact=(\S+)\s+facscfact=(\S+)\s+lhapdf=(\d+)',
248 r'MUR\1_MUF\2_PDF\3', weight_name)
249 weight_mappings[weight_id] = renamed_name
250 # Replace the weight name in the line
251 line = line.replace(weight_name, renamed_name)
252 # remove all left spaces except for "weight id"
253 match = re.search(r"<weight id='(.*?)</weight>", line)
254 if match:
255 removed_spaces = match.group(1).replace(" ", "")
256 line = line.replace(match.group(1), removed_spaces)
257
258 elif '</header>' in line:
259 # After the header, add the NNLO weight IDs to the weight_ids
260 for nnlo_id, _, _ in NNLO_weight_list:
261 weight_ids.append(nnlo_id)
262
263
264 # Write the processed line to the output file
265 outfile.write(line)
266
267
268def reformat_DYNNLO_events(powheg_LHE_output, input_LHE_name):
269 """! Reformat DYNNLO events to fit ATLAS conventions."""
270 logger.info("Converting output to LHEv3 format")
271
272 # Extract intro, header and footer
273 intro = "\n".join([elem for elem in [LHE.opening_tag(input_LHE_name), LHE.comment_block(input_LHE_name)] if elem])
274# header = LHE.header_block(input_LHE_events)
275# init = LHE.init_block(input_LHE_events)
276 header = LHE.header_block(input_LHE_name)
277 init = LHE.init_block(input_LHE_name)
278 closing_string = LHE.postamble(input_LHE_name)
279 if closing_string.find("<LesHouchesEvents>") != -1:
280 footer = closing_string[closing_string.find("</LesHouchesEvents>"):]
281 else:
282 footer = "</LesHouchesEvents>"
283
284 # Add nominal weight to header
285 logger.info("Adding LHEv3 weight: nominal")
286 header_elem = LHE.add_weight_to_header(header, "nominal", "nominal", 0)
287
288 # Get list of any weights already used in the range 4000-5000
289 weight_name_to_id = {}
290 try:
291 w_elems = header_elem.iter(tag="weight") # needs python >= 2.7
292 except AttributeError:
293 w_elems = header_elem.getiterator(tag="weight")
294 weight_number_offset = max(filter(lambda w: 4000 <= w < 5000, [int(w_elem.attrib["id"]) for w_elem in w_elems]) + [4000])
295
296 # Add DYNNLO weights to header
297 dynnlo_weight_names = [x[0] for x in LHE.string_to_weight(LHE.get_first_event(input_LHE_name))]
298 for idx, weight_name in enumerate(dynnlo_weight_names, start=1):
299 logger.info("Adding LHEv3 weight: {}".format(weight_name))
300 weight_name_to_id[weight_name] = weight_number_offset + idx
301 header_elem = LHE.add_weight_to_header(header_elem, "dynnlo", weight_name, weight_name_to_id[weight_name])
302
303 # Convert Powheg input events to LHEv3 output ones
304 logger.info("Converting Powheg output into LHEv3 format")
305 with open(powheg_LHE_output, "w") as f_output:
306 f_output.write("{}\n".format(intro))
307 ElementTree.ElementTree(header_elem).write(f_output)
308 f_output.write("{}\n".format(init))
309 for event in LHE.event_iterator(input_LHE_name):
310 f_output.write(LHE.Powheg2LHEv3(event, weight_name_to_id))
311 f_output.write(footer)
312
313
314def construct_NNLOPS_weight_list(NNLO_output_weights, powheg_LHE_output):
315 """! Construct list of NNLO weights."""
316 # Construct list of tuples (weight_ID, weight_name) for existing weights
317 existing_weights, NNLO_weights = [], []
318 with open(powheg_LHE_output, "r") as f_input:
319 for line in f_input:
320 if "weight id=" in line:
321 existing_weights.append((str(int(line.split("'")[1])), line.split(">")[1].split("<")[0].strip()))
322
323 # Construct weight descriptor sets for consistent ordering
324 logger.info("Constructing list of weights")
325 for idx, (NNLO_weight_name, NNLOPS_command) in enumerate(NNLO_output_weights.items(), start=4001):
326 NNLOPS_command = NNLOPS_command.replace("muR = 1.0, muF = 1.0", "nominal")
327 # Check whether any of the NNLO descriptions refer to existing weights
328 for weight_ID, weight_name in existing_weights:
329 # Check for both '$description' and "$description"
330 re_match = re.search(r"""["']{}["']""".format(weight_name), NNLOPS_command)
331 if re_match is not None:
332 NNLOPS_command = NNLOPS_command.replace(re_match.group(0), re_match.group(0).replace(weight_name, weight_ID))
333 logger.info("... identified '{}' as weight ID '{}'".format(weight_name, weight_ID))
334 # Add this to the list of known weights
335 existing_weights.append((idx, NNLO_weight_name))
336 NNLO_weights.append((idx, NNLO_weight_name, NNLOPS_command))
337 return NNLO_weights
void print(char *figname, TCanvas *c1)
#define max(a, b)
Definition cfImp.cxx:41
Wrapper to handle multiple Powheg subprocesses.
Single executable running in a subprocess (usually PowhegBox).
std::string replace(std::string s, const std::string &s2, const std::string &s3)
Definition hcg.cxx:310
std::vector< std::string > split(const std::string &s, const std::string &t=":")
Definition hcg.cxx:177
rename_weights_toNNLOPS(input_filename, output_filename, powheg_LHE_output)
run_NNLO_executable(process, powheg_LHE_output, output_LHE_name)
Run the NNLO executable.
NNLO_reweighter(process, powheg_LHE_output)
Move output to correctly named file.
acquire_NNLO_inputs(aux_directory, NNLO_reweighting_inputs)
Get reweighting files from AuxFiles directory if not provided with job.
run_NNLOPS_executable(NNLO_executable, NNLO_reweighting_inputs, NNLO_weight_list, powheg_LHE_output, output_LHE_name)
Run NNLOPSreweighter with appropriate run card.
reformat_NNLO_events(process, powheg_LHE_output, output_LHE_name)
Reformat NNLOPS and DYNNLO events.
reformat_NNLOPS_events(NNLO_weight_list, powheg_LHE_output, output_LHE_name)
Reformat NNLOPS events to fit ATLAS conventions.
run_DYNNLO_executable(NNLO_executable, NNLO_reweighting_inputs, powheg_LHE_output, output_LHE_name)
Run DYNNLOPS reweighter with appropriate arguments.
construct_NNLOPS_weight_list(NNLO_output_weights, powheg_LHE_output)
Construct list of NNLO weights.
reformat_DYNNLO_events(powheg_LHE_output, input_LHE_name)
Reformat DYNNLO events to fit ATLAS conventions.