ATLAS Offline Software
Loading...
Searching...
No Matches
Herwig7ConfigDecoder.py
Go to the documentation of this file.
1# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
2# Author: Lukas Kretschmann (lukas.kretschmann@cern.ch)
3
4# Import standard Python modules for file handling, subprocess execution, and path manipulation
5import subprocess
6import os
7from pathlib import Path
8
9# Import Athena-specific modules for logging
10from AthenaCommon import Logging
11athMsgLog = Logging.logging.getLogger('Herwig7ConfigDecoder')
12
13def extract_herwig_lines(input_file="Herwig.run", temp_file="HerwigConfigDecoder_RunCardDump.txt", skip_particles=False, skip_decays=False):
14 """Extract unique lines containing '/Herwig/' (excluding 'cvmfs') from the input file and save to a temporary file.
15
16 Args:
17 input_file (str): Path to the input Herwig run card file (default: 'Herwig.run').
18 temp_file (str): Path to the temporary output file for extracted lines (default: 'HerwigConfigDecoder_RunCardDump.txt').
19 skip_particles (bool): If True, skip lines related to /Herwig/Particles, /Herwig/Masses, and /Herwig/Widths.
20 skip_decays (bool): If True, skip lines related to /Herwig/Decays.
21
22 Returns:
23 list: List of unique Herwig configuration lines extracted.
24 """
25 try:
26 # Read the input file and filter lines containing '/Herwig/' while excluding 'cvmfs'
27 with open(input_file, 'r') as infile:
28 all_lines = [line.strip() for line in infile if '/Herwig/' in line and 'cvmfs' not in line]
29 herwig_lines = []
30 seen_paths = set() # Track unique paths to avoid duplicates
31 skip_particles_count = 0
32 skip_decays_count = 0
33 for line in all_lines:
34 # Skip particle-related lines if specified
35 if skip_particles and ('/Herwig/Particles' in line or '/Herwig/Masses' in line or '/Herwig/Widths' in line):
36 skip_particles_count += 1
37 continue
38 # Skip decay-related lines if specified
39 if skip_decays and '/Herwig/Decays' in line:
40 skip_decays_count += 1
41 continue
42 # Skip duplicate lines
43 if line in seen_paths:
44 athMsgLog.debug(f"Skipped duplicate path: {line}")
45 continue
46 seen_paths.add(line)
47 herwig_lines.append(line)
48 # Clean up specific Herwig path formatting issues
49 for i in range(len(herwig_lines)):
50 if '|}/Herwig' in herwig_lines[i]:
51 herwig_lines[i] = herwig_lines[i].replace('|}/Herwig', '/Herwig')
52 elif '}/Herwig' in herwig_lines[i]:
53 herwig_lines[i] = herwig_lines[i].replace('}/Herwig', '/Herwig')
54
55 # Log information about skipped lines
56 skipped_count = len(all_lines) - len(herwig_lines)
57 if skipped_count > 0:
58 athMsgLog.debug(f"Skipped {skipped_count} lines (including {len(all_lines) - len(seen_paths) - skip_particles_count - skip_decays_count} duplicates)")
59 if skip_particles_count > 0:
60 athMsgLog.debug(f"Skipped {skip_particles_count} /Herwig/Particles, /Herwig/Masses and /Herwig/Widths lines due to --skip-particles")
61 if skip_decays_count > 0:
62 athMsgLog.debug(f"Skipped {skip_decays_count} /Herwig/Decays lines due to --skip-decays")
63
64 # Write the filtered unique lines to the temporary file
65 with open(temp_file, 'w') as outfile:
66 outfile.writelines(line + '\n' for line in herwig_lines)
67
68 athMsgLog.info(f"Successfully extracted {len(herwig_lines)} unique lines to {temp_file}")
69 return herwig_lines
70 except FileNotFoundError:
71 athMsgLog.info(f"Error: Input file '{input_file}' not found")
72 return []
73 except Exception as e:
74 athMsgLog.info(f"Error: An error occurred: {str(e)}")
75 return []
76
77def run_herwig_get_commands(commands, phase="paths"):
78 """Execute multiple Herwig 'get' commands in a single shell session and parse the output.
79
80 Args:
81 commands (list): List of commands or tuples (for interfaces) to execute.
82 phase (str): Execution phase, either 'paths' for path commands or 'interfaces' for interface commands.
83
84 Returns:
85 list: List of tuples containing the command and its output or error message.
86 """
87 if not commands:
88 return []
89
90 # Retrieve Herwig installation path from environment variable
91 herwig_path = os.getenv('HERWIG7_PATH')
92 if not herwig_path:
93 return [(cmd, "Error: Environment variable HERWIG7_PATH is not set") for cmd in commands]
94
95 # Construct the Herwig command to read the default repository
96 herwig_cmd = f"{herwig_path}/bin/Herwig read --repo={herwig_path}/share/Herwig/HerwigDefaults.rpo"
97
98 try:
99 # Create a temporary script file to store the commands
100 temp_script = f"temp_herwig_script_{os.getpid()}.in"
101 with open(temp_script, 'w') as f:
102 for cmd in commands:
103 if phase == "paths":
104 f.write(f"get {cmd}\n")
105 else: # interfaces
106 f.write(f"get {cmd[0]}:{cmd[1]}\n")
107
108 # Execute the Herwig command with the temporary script as input
109 cmd = f"cat {temp_script} | {herwig_cmd}"
110 result = subprocess.run(
111 cmd,
112 shell=True,
113 capture_output=True,
114 text=True,
115 timeout=10 * len(commands) # Scale timeout based on number of commands
116 )
117
118 # Process the command output
119 output = result.stdout.strip()
120 if result.stderr:
121 output += f"\nError: {result.stderr.strip()}"
122
123 # Clean up the temporary script file
124 Path(temp_script).unlink()
125
126 # Split output into individual command responses
127 outputs = output.split("Herwig> get ")[1:] if output else []
128 results = []
129 for i, cmd in enumerate(commands):
130 if i < len(outputs):
131 raw_output = outputs[i].strip()
132 # Remove the command itself from the output
133 cmd_str = cmd if phase == "paths" else f"{cmd[0]}:{cmd[1]}"
134 if raw_output.startswith(cmd_str):
135 raw_output = raw_output[len(cmd_str):].strip()
136 if phase == "interfaces":
137 # Apply filtering for interface outputs
138 if len(raw_output.split("\n")) == 1:
139 if raw_output.split("\n")[0] != '' and raw_output.split("\n")[0] != 'Herwig>':
140 filtered_output = raw_output.split("\n")[0]
141 elif raw_output.split("\n")[0] != 'Herwig>':
142 filtered_output = "No response from the interface"
143 else:
144 filtered_output = "No response from the interface"
145 elif len(raw_output.split("\n")) == 2:
146 filtered_output = "No response from the interface"
147 elif len(raw_output.split("\n")) == 3:
148 if all("Herwig" in line for line in raw_output.split("\n")):
149 filtered_output = "No response from the interface"
150 else:
151 filtered_output = raw_output.split("\n")[1]
152 else:
153 filtered_output = raw_output
154 results.append((cmd[0], f"{cmd[0]}:{cmd[1]}", filtered_output))
155 else:
156 results.append((cmd, raw_output))
157 else:
158 if phase == "interfaces":
159 results.append((cmd[0], f"{cmd[0]}:{cmd[1]}", "Error: No output received"))
160 else:
161 results.append((cmd, "Error: No output received"))
162
163 return results
164
165 except subprocess.TimeoutExpired:
166 return [(cmd, "Error: Command timed out") if phase == "paths" else (cmd[0], f"{cmd[0]}:{cmd[1]}", "Error: Command timed out") for cmd in commands]
167 except Exception as e:
168 return [(cmd, f"Error: {str(e)}") if phase == "paths" else (cmd[0], f"{cmd[0]}:{cmd[1]}", f"Error: {str(e)}") for cmd in commands]
169
170def execute_herwig_get(herwig_lines, output_file="HerwigConfigDecoder_InterfaceDump.txt", max_lines=None):
171 """Execute Herwig 'get' commands for all provided paths in a single shell session and save the results.
172
173 Args:
174 herwig_lines (list): List of Herwig configuration paths to process.
175 output_file (str): Path to the output file for results (default: 'HerwigConfigDecoder_InterfaceDump.txt').
176 max_lines (int, optional): Maximum number of lines to process (default: None, processes all lines).
177 """
178 try:
179 # Check if Herwig path is set
180 herwig_path = os.getenv('HERWIG7_PATH')
181 if not herwig_path:
182 athMsgLog.error("Error: Environment variable HERWIG7_PATH is not set")
183 return
184
185 # Limit the number of lines to process if specified
186 lines_to_process = herwig_lines[:max_lines] if max_lines is not None else herwig_lines
187
188 # Open the output file and process the Herwig paths
189 with open(output_file, 'w') as outfile:
190 total_tasks = len(lines_to_process)
191 athMsgLog.info(f"Processing {total_tasks} Herwig paths")
192 results = run_herwig_get_commands(lines_to_process, phase="paths")
193
194 # Sort results to maintain original order
195 results.sort(key=lambda x: lines_to_process.index(x[0]) if x[0] in lines_to_process else float('inf'))
196
197 # Write results to the output file
198 for line, output in results:
199 if not line:
200 continue
201 outfile.write(f"{line}\n{output}\n\n")
202 athMsgLog.debug(f"Success! Parsed the options for: {line}")
203
204 athMsgLog.info(f"Results saved to {output_file} (processed {len(lines_to_process)} lines)")
205
206 except Exception as e:
207 athMsgLog.info(f"Error: An error occurred during execution: {str(e)}")
208
209def process_valid_interfaces(input_file="HerwigConfigDecoder_InterfaceDump.txt", output_file="HerwigConfigDecoder_ConfigParameters.txt"):
210 """Process valid interfaces for each /Herwig/ path, execute 'get' commands, and save results.
211
212 Args:
213 input_file (str): Path to the input file containing Herwig paths and interfaces (default: 'HerwigConfigDecoder_InterfaceDump.txt').
214 output_file (str): Path to the output file for interface results (default: 'HerwigConfigDecoder_ConfigParameters.txt').
215 """
216 try:
217 # Check if Herwig path is set
218 herwig_path_env = os.getenv('HERWIG7_PATH')
219 if not herwig_path_env:
220 athMsgLog.error("Error: Environment variable HERWIG7_PATH is not set")
221 return
222
223 # Read the input file and process each block of Herwig paths and interfaces
224 with open(input_file, 'r') as infile, open(output_file, 'w') as outfile:
225 content = infile.read()
226 blocks = content.split('\n\n')
227
228 all_tasks = []
229 block_paths = []
230 errors = [] # Buffer for "No valid interfaces" errors
231 seen_paths = set() # Track unique paths to avoid duplicates
232 for block in blocks:
233 lines = block.split('\n')
234 if not lines or not lines[0].startswith('/Herwig/'):
235 continue
236
237 herwig_path = lines[0].strip()
238 if herwig_path in seen_paths:
239 athMsgLog.debug(f"Skipped duplicate path in herwig_results.txt: {herwig_path}")
240 continue
241 seen_paths.add(herwig_path)
242 block_paths.append(herwig_path)
243 # Extract valid interfaces from the block
244 valid_interfaces = [
245 line.strip()[2:] for line in lines[1:]
246 if line.strip().startswith('* ')
247 ]
248
249 if not valid_interfaces:
250 errors.append((herwig_path, f"No valid interfaces found for {herwig_path}"))
251 outfile.write(f"{herwig_path}\nNo valid interfaces found\n\n")
252 continue
253
254 all_tasks.extend([(herwig_path, interface) for interface in valid_interfaces])
255
256 if not all_tasks:
257 athMsgLog.info("No interfaces to process")
258 # Log any buffered errors before returning
259 for _, error_msg in errors:
260 athMsgLog.debug(error_msg)
261 return
262
263 # Process all interfaces in a single shell session
264 athMsgLog.info(f"Processing {len(all_tasks)} interfaces")
265 results = run_herwig_get_commands(all_tasks, phase="interfaces")
266
267 # Log buffered errors
268 for _, error_msg in errors:
269 athMsgLog.debug(error_msg)
270
271 # Organize results by Herwig path
272 path_results = {}
273 for herwig_path, get_command, filtered_output in results:
274 if herwig_path not in path_results:
275 path_results[herwig_path] = []
276 path_results[herwig_path].append((get_command, filtered_output))
277
278 # Write results to the output file
279 for herwig_path in block_paths:
280 if herwig_path not in path_results:
281 continue
282 athMsgLog.debug(f"Processing interfaces for {herwig_path}:")
283 outfile.write(f"{herwig_path}\n")
284 for get_command, filtered_output in path_results[herwig_path]:
285 athMsgLog.debug(f"{get_command} ::: {filtered_output}")
286 outfile.write(f"{get_command} ::: {filtered_output}\n")
287 outfile.write("\n")
288
289 athMsgLog.info(f"Interface results saved to {output_file}")
290
291 except FileNotFoundError:
292 athMsgLog.info(f"Error: Input file '{input_file}' not found")
293 except Exception as e:
294 athMsgLog.info(f"Error: An error occurred during interface processing: {str(e)}")
295
296def remove_duplicate_blocks(input_file="HerwigConfigDecoder_ConfigParameters.txt"):
297 """Remove duplicate blocks in the configuration parameters file, keeping the first occurrence.
298
299 Args:
300 input_file (str): Path to the input file to process (default: 'HerwigConfigDecoder_ConfigParameters.txt').
301 """
302 try:
303 # Read the input file and split into blocks
304 with open(input_file, 'r') as infile:
305 content = infile.read()
306 blocks = content.rstrip().split('\n\n')
307
308 # Keep the first block for each unique Herwig path
309 path_blocks = {}
310 for block in blocks:
311 lines = block.split('\n')
312 if not lines or not lines[0].startswith('/Herwig/'):
313 continue
314 herwig_path = lines[0].strip()
315 if herwig_path not in path_blocks:
316 path_blocks[herwig_path] = block
317 else:
318 athMsgLog.debug(f"Removed duplicate block for {herwig_path}")
319
320 # Collect unique blocks in their original order
321 unique_blocks = []
322 seen_paths = set()
323 for block in blocks:
324 lines = block.split('\n')
325 if not lines or not lines[0].startswith('/Herwig/'):
326 continue
327 herwig_path = lines[0].strip()
328 if herwig_path not in seen_paths:
329 seen_paths.add(herwig_path)
330 unique_blocks.append(path_blocks[herwig_path])
331
332 # Write the unique blocks back to the file
333 with open(input_file, 'w') as outfile:
334 outfile.write('\n\n'.join(unique_blocks) + '\n')
335
336 athMsgLog.info(f"Removed duplicate blocks in {input_file}")
337 athMsgLog.info(f"Retained {len(unique_blocks)} unique blocks")
338
339 except FileNotFoundError:
340 athMsgLog.info(f"Error: Input file '{input_file}' not found")
341 except Exception as e:
342 athMsgLog.info(f"Error: An error occurred while removing duplicate blocks: {str(e)}")
343
344def DecodeRunCard(input_file='Herwig.run', skip_particles=False, skip_decays=False):
345 """Main function to decode a Herwig run card by extracting paths, executing commands, and processing interfaces.
346
347 Args:
348 input_file (str): Path to the Herwig run card file (default: 'Herwig.run').
349 skip_particles (bool): If True, skip particle-related lines.
350 skip_decays (bool): If True, skip decay-related lines.
351 """
352 athMsgLog.info("Hello from the config-decoder!")
353
354 # Step 1: Extract Herwig configuration lines
355 herwig_lines = extract_herwig_lines(
356 input_file=input_file,
357 skip_particles=skip_particles,
358 skip_decays=skip_decays
359 )
360
361 # Step 2: Execute Herwig 'get' commands for the extracted paths
362 if herwig_lines:
363 execute_herwig_get(herwig_lines)
364
365 # Step 3: Process valid interfaces for the paths
367
368 # Step 4: Remove any duplicate blocks in the final output
370
371 athMsgLog.info("Goodbye from the config-decoder!")
STL class.
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
remove_duplicate_blocks(input_file="HerwigConfigDecoder_ConfigParameters.txt")
run_herwig_get_commands(commands, phase="paths")
extract_herwig_lines(input_file="Herwig.run", temp_file="HerwigConfigDecoder_RunCardDump.txt", skip_particles=False, skip_decays=False)
execute_herwig_get(herwig_lines, output_file="HerwigConfigDecoder_InterfaceDump.txt", max_lines=None)
process_valid_interfaces(input_file="HerwigConfigDecoder_InterfaceDump.txt", output_file="HerwigConfigDecoder_ConfigParameters.txt")
DecodeRunCard(input_file='Herwig.run', skip_particles=False, skip_decays=False)